mirror of
https://github.com/simplex-chat/simplex-chat.git
synced 2024-12-17 17:20:21 +01:00
Compare commits
44 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| ace687b7d3 | |||
| 59b5d07252 | |||
| 82400aac66 | |||
| 7df7434a92 | |||
| f243f01bd1 | |||
| 6607c24de3 | |||
| 4086238090 | |||
| a1bbecc7ad | |||
| 78510b6fd3 | |||
| efa0d6d551 | |||
| 17214a55e9 | |||
| edf99fcd1d | |||
| 2ffabd1ef8 | |||
| 28383edb83 | |||
| 9175897acf | |||
| f3cd167502 | |||
| 7cde2cf6c2 | |||
| 3913043705 | |||
| a160acef12 | |||
| c54fae0136 | |||
| d57abfcc93 | |||
| 515a0ddfdd | |||
| b5d8c65249 | |||
| de94892fe7 | |||
| b7131e16f2 | |||
| 11a44dc1fd | |||
| 0af718f03f | |||
| f8f5c3c6be | |||
| 13912a4af9 | |||
| 601b4cd619 | |||
| d2b4b7bed6 | |||
| fa95e4e9ad | |||
| 88fdc1ef75 | |||
| 7ab6e44a6e | |||
| 26986686ca | |||
| e76dc33cf0 | |||
| aa2eafdacb | |||
| 2c3c97f5cc | |||
| 83f42704ea | |||
| 7ff6ef09fe | |||
| dbe4504f05 | |||
| 9a87f344b5 | |||
| 2127c7dcce | |||
| e3528d3ffe |
@@ -131,6 +131,10 @@ jobs:
|
||||
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 " flags: +openssl" >> cabal.project.local
|
||||
echo "" >> cabal.project.local
|
||||
echo "package rocksdb-haskell-jprupp" >> cabal.project.local
|
||||
echo " extra-include-dirs: /opt/homebrew/opt/rocksdb/include" >> cabal.project.local
|
||||
echo " extra-lib-dirs: /opt/homebrew/opt/rocksdb/lib" >> cabal.project.local
|
||||
|
||||
- name: Unix prepare cabal.project.local for Mac
|
||||
if: matrix.os == 'macos-13'
|
||||
@@ -145,14 +149,22 @@ jobs:
|
||||
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 " flags: +openssl" >> cabal.project.local
|
||||
echo "" >> cabal.project.local
|
||||
echo "package rocksdb-haskell-jprupp" >> cabal.project.local
|
||||
echo " extra-include-dirs: /opt/homebrew/opt/rocksdb/include" >> cabal.project.local
|
||||
echo " extra-lib-dirs: /opt/homebrew/opt/rocksdb/lib" >> 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 package dependencies
|
||||
if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04'
|
||||
run: sudo apt install -y librocksdb-dev
|
||||
|
||||
- name: Install pkg-config for Mac
|
||||
if: matrix.os == 'macos-latest' || matrix.os == 'macos-13'
|
||||
run: brew install pkg-config
|
||||
run: brew install pkg-config rocksdb
|
||||
|
||||
- name: Unix prepare cabal.project.local for Ubuntu
|
||||
if: matrix.os == 'ubuntu-20.04' || matrix.os == 'ubuntu-22.04'
|
||||
@@ -322,6 +334,7 @@ jobs:
|
||||
git
|
||||
perl
|
||||
make
|
||||
mingw-w64-x86_64-rocksdb
|
||||
pacboy: >-
|
||||
toolchain:p
|
||||
cmake:p
|
||||
@@ -341,6 +354,10 @@ jobs:
|
||||
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 "" >> cabal.project.local
|
||||
echo "package rocksdb-haskell-jprupp" >> cabal.project.local
|
||||
echo " extra-include-dirs: /mingw64/include" >> cabal.project.local
|
||||
echo " extra-lib-dirs: /mingw64/lib" >> cabal.project.local
|
||||
|
||||
rm -rf dist-newstyle/src/direct-sq*
|
||||
sed -i "s/, unix /--, unix /" simplex-chat.cabal
|
||||
|
||||
+4
-2
@@ -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
|
||||
|
||||
@@ -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.
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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?
|
||||
|
||||
@@ -29,17 +29,33 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
private var granted = false
|
||||
private var prevNtfTime: Dictionary<ChatId, Date> = [:]
|
||||
|
||||
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,
|
||||
@@ -185,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")
|
||||
)
|
||||
])
|
||||
}
|
||||
@@ -210,29 +230,28 @@ class NtfManager: NSObject, UNUserNotificationCenterDelegate, ObservableObject {
|
||||
}
|
||||
}
|
||||
}
|
||||
center.delegate = self
|
||||
}
|
||||
|
||||
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) {
|
||||
|
||||
@@ -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)
|
||||
@@ -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(
|
||||
|
||||
@@ -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()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
File diff suppressed because one or more lines are too long
@@ -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))
|
||||
}
|
||||
|
||||
@@ -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<Bool>,
|
||||
allowMenu: Binding<Bool> = .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<Content: View>: 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<Content: View>: 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<Content: View>: 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)
|
||||
}
|
||||
|
||||
@@ -1183,11 +1183,11 @@ 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))
|
||||
.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 {
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -78,7 +78,7 @@ struct ReverseList<Content: View>: UIViewControllerRepresentable {
|
||||
self.dataSource = UITableViewDiffableDataSource<Section, ChatItem>(
|
||||
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)
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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))
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 {
|
||||
|
||||
@@ -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<Value: Equatable>: 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) }
|
||||
}
|
||||
@@ -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())
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
@@ -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)")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -1087,6 +1087,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better calls" xml:space="preserve">
|
||||
<source>Better calls</source>
|
||||
<target>Verbesserte Anrufe</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better groups" xml:space="preserve">
|
||||
@@ -1096,6 +1097,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better message dates." xml:space="preserve">
|
||||
<source>Better message dates.</source>
|
||||
<target>Verbesserte Nachrichten-Datumsinformation</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better messages" xml:space="preserve">
|
||||
@@ -1110,14 +1112,17 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better notifications" xml:space="preserve">
|
||||
<source>Better notifications</source>
|
||||
<target>Verbesserte Benachrichtigungen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better security ✅" xml:space="preserve">
|
||||
<source>Better security ✅</source>
|
||||
<target>Verbesserte Sicherheit ✅</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better user experience" xml:space="preserve">
|
||||
<source>Better user experience</source>
|
||||
<target>Verbesserte Nutzer-Erfahrung</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Black" xml:space="preserve">
|
||||
@@ -1942,6 +1947,7 @@ Das ist Ihr eigener Einmal-Link!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Customizable message shape." xml:space="preserve">
|
||||
<source>Customizable message shape.</source>
|
||||
<target>Anpassbares Format des Nachrichtenfelds</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Customize theme" xml:space="preserve">
|
||||
@@ -2240,6 +2246,7 @@ Das ist Ihr eigener Einmal-Link!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete or moderate up to 200 messages." xml:space="preserve">
|
||||
<source>Delete or moderate up to 200 messages.</source>
|
||||
<target>Bis zu 200 Nachrichten löschen oder moderieren</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete pending connection?" xml:space="preserve">
|
||||
@@ -3367,6 +3374,7 @@ Das ist Ihr eigener Einmal-Link!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forward up to 20 messages at once." xml:space="preserve">
|
||||
<source>Forward up to 20 messages at once.</source>
|
||||
<target>Bis zu 20 Nachrichten auf einmal weiterleiten</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarded" xml:space="preserve">
|
||||
@@ -3761,6 +3769,8 @@ Fehler: %2$@</target>
|
||||
<trans-unit id="Improved delivery, reduced traffic usage. More improvements are coming soon!" xml:space="preserve">
|
||||
<source>Improved delivery, reduced traffic usage.
|
||||
More improvements are coming soon!</source>
|
||||
<target>Verbesserte Nachrichten-Auslieferung und verringerter Datenverbrauch.
|
||||
Weitere Verbesserungen sind bald verfügbar!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved message delivery" xml:space="preserve">
|
||||
@@ -6463,6 +6473,7 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
|
||||
<source>SimpleX protocols reviewed by Trail of Bits.</source>
|
||||
<target>Die SimpleX-Protokolle wurden von Trail of Bits überprüft.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Simplified incognito mode" xml:space="preserve">
|
||||
@@ -6642,10 +6653,12 @@ Aktivieren Sie es in den *Netzwerk & Server* Einstellungen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Switch audio and video during the call." xml:space="preserve">
|
||||
<source>Switch audio and video during the call.</source>
|
||||
<target>Während des Anrufs zwischen Audio und Video wechseln</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Switch chat profile for 1-time invitations." xml:space="preserve">
|
||||
<source>Switch chat profile for 1-time invitations.</source>
|
||||
<target>Das Chat-Profil für Einmal-Einladungen wechseln</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="System" xml:space="preserve">
|
||||
|
||||
@@ -1087,6 +1087,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better calls" xml:space="preserve">
|
||||
<source>Better calls</source>
|
||||
<target>Llamadas mejoradas</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better groups" xml:space="preserve">
|
||||
@@ -1096,6 +1097,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better message dates." xml:space="preserve">
|
||||
<source>Better message dates.</source>
|
||||
<target>Sistema de fechas mejorado.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better messages" xml:space="preserve">
|
||||
@@ -1110,14 +1112,17 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better notifications" xml:space="preserve">
|
||||
<source>Better notifications</source>
|
||||
<target>Notificaciones mejoradas</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better security ✅" xml:space="preserve">
|
||||
<source>Better security ✅</source>
|
||||
<target>Seguridad mejorada ✅</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better user experience" xml:space="preserve">
|
||||
<source>Better user experience</source>
|
||||
<target>Experiencia de usuario mejorada</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Black" xml:space="preserve">
|
||||
@@ -1942,6 +1947,7 @@ This is your own one-time link!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Customizable message shape." xml:space="preserve">
|
||||
<source>Customizable message shape.</source>
|
||||
<target>Forma personalizable de los mensajes.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Customize theme" xml:space="preserve">
|
||||
@@ -2240,6 +2246,7 @@ This is your own one-time link!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete or moderate up to 200 messages." xml:space="preserve">
|
||||
<source>Delete or moderate up to 200 messages.</source>
|
||||
<target>Borra o modera hasta 200 mensajes a la vez.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete pending connection?" xml:space="preserve">
|
||||
@@ -3367,6 +3374,7 @@ This is your own one-time link!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forward up to 20 messages at once." xml:space="preserve">
|
||||
<source>Forward up to 20 messages at once.</source>
|
||||
<target>Desplazamiento de hasta 20 mensajes.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarded" xml:space="preserve">
|
||||
@@ -3761,6 +3769,8 @@ Error: %2$@</target>
|
||||
<trans-unit id="Improved delivery, reduced traffic usage. More improvements are coming soon!" xml:space="preserve">
|
||||
<source>Improved delivery, reduced traffic usage.
|
||||
More improvements are coming soon!</source>
|
||||
<target>Reducción del tráfico y entrega mejorada.
|
||||
¡Pronto habrá nuevas mejoras!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved message delivery" xml:space="preserve">
|
||||
@@ -6463,6 +6473,7 @@ Actívalo en ajustes de *Servidores y Redes*.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
|
||||
<source>SimpleX protocols reviewed by Trail of Bits.</source>
|
||||
<target>Protocolos de SimpleX auditados por Trail of Bits.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Simplified incognito mode" xml:space="preserve">
|
||||
@@ -6642,10 +6653,12 @@ Actívalo en ajustes de *Servidores y Redes*.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Switch audio and video during the call." xml:space="preserve">
|
||||
<source>Switch audio and video during the call.</source>
|
||||
<target>Intercambia audio y video durante la llamada.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Switch chat profile for 1-time invitations." xml:space="preserve">
|
||||
<source>Switch chat profile for 1-time invitations.</source>
|
||||
<target>Cambia el perfil de chat para invitaciones de un solo uso.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="System" xml:space="preserve">
|
||||
|
||||
@@ -762,7 +762,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="All your contacts will remain connected. Profile update will be sent to your contacts." xml:space="preserve">
|
||||
<source>All your contacts will remain connected. Profile update will be sent to your contacts.</source>
|
||||
<target>Az ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél.</target>
|
||||
<target>Az ismerőseivel kapcsolatban marad. A profil-változtatások frissítésre kerülnek az ismerősöknél.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." xml:space="preserve">
|
||||
@@ -1087,6 +1087,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better calls" xml:space="preserve">
|
||||
<source>Better calls</source>
|
||||
<target>Továbbfejlesztett hívásélmény</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better groups" xml:space="preserve">
|
||||
@@ -1096,6 +1097,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better message dates." xml:space="preserve">
|
||||
<source>Better message dates.</source>
|
||||
<target>Továbbfejlesztett üzenetdátumok.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better messages" xml:space="preserve">
|
||||
@@ -1110,14 +1112,17 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better notifications" xml:space="preserve">
|
||||
<source>Better notifications</source>
|
||||
<target>Továbbfejlesztett értesítések</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better security ✅" xml:space="preserve">
|
||||
<source>Better security ✅</source>
|
||||
<target>Továbbfejlesztett biztonság ✅</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better user experience" xml:space="preserve">
|
||||
<source>Better user experience</source>
|
||||
<target>Továbbfejlesztett felhasználói élmény</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Black" xml:space="preserve">
|
||||
@@ -1408,7 +1413,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat profile" xml:space="preserve">
|
||||
<source>Chat profile</source>
|
||||
<target>Felhasználói profil</target>
|
||||
<target>Csevegési profil</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Chat theme" xml:space="preserve">
|
||||
@@ -1433,7 +1438,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Choose _Migrate from another device_ on the new device and scan QR code." xml:space="preserve">
|
||||
<source>Choose _Migrate from another device_ on the new device and scan QR code.</source>
|
||||
<target>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.</target>
|
||||
<target>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.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Choose file" xml:space="preserve">
|
||||
@@ -1717,7 +1722,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection timeout" xml:space="preserve">
|
||||
<source>Connection timeout</source>
|
||||
<target>Kapcsolat időtúllépés</target>
|
||||
<target>Időtúllépés kapcsolódáskor</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Connection with desktop stopped" xml:space="preserve">
|
||||
@@ -1942,6 +1947,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Customizable message shape." xml:space="preserve">
|
||||
<source>Customizable message shape.</source>
|
||||
<target>Testreszabható üzenetbuborékok.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Customize theme" xml:space="preserve">
|
||||
@@ -1971,7 +1977,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database IDs and Transport isolation option." xml:space="preserve">
|
||||
<source>Database IDs and Transport isolation option.</source>
|
||||
<target>Adatbázis-azonosítók és átviteli izolációs beállítások.</target>
|
||||
<target>Adatbázis-azonosítók és átvitel-izolációs beállítások.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Database downgrade" xml:space="preserve">
|
||||
@@ -2240,6 +2246,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete or moderate up to 200 messages." xml:space="preserve">
|
||||
<source>Delete or moderate up to 200 messages.</source>
|
||||
<target>Legfeljebb 200 üzenet egyszerre való törlése, vagy moderálása.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete pending connection?" xml:space="preserve">
|
||||
@@ -2349,7 +2356,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Details" xml:space="preserve">
|
||||
<source>Details</source>
|
||||
<target>Részletek</target>
|
||||
<target>További részletek</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Develop" xml:space="preserve">
|
||||
@@ -2384,7 +2391,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Different names, avatars and transport isolation." xml:space="preserve">
|
||||
<source>Different names, avatars and transport isolation.</source>
|
||||
<target>Különböző nevek, avatarok és átviteli izoláció.</target>
|
||||
<target>Különböző nevek, profilképek és átvitel-izoláció.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Direct messages" xml:space="preserve">
|
||||
@@ -2910,7 +2917,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error deleting user profile" xml:space="preserve">
|
||||
<source>Error deleting user profile</source>
|
||||
<target>Hiba a felhasználói profil törlésekor</target>
|
||||
<target>Hiba a felhasználó-profil törlésekor</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error downloading the archive" xml:space="preserve">
|
||||
@@ -3025,7 +3032,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error saving user password" xml:space="preserve">
|
||||
<source>Error saving user password</source>
|
||||
<target>Hiba a felhasználó jelszavának mentésekor</target>
|
||||
<target>Hiba a felhasználói jelszó mentésekor</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error scanning code: %@" xml:space="preserve">
|
||||
@@ -3095,7 +3102,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error updating user privacy" xml:space="preserve">
|
||||
<source>Error updating user privacy</source>
|
||||
<target>Hiba a felhasználói beállítások frissítésekor</target>
|
||||
<target>Hiba a felhasználói adatvédelem frissítésekor</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Error uploading the archive" xml:space="preserve">
|
||||
@@ -3367,6 +3374,7 @@ Ez az Ön egyszer használható hivatkozása!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forward up to 20 messages at once." xml:space="preserve">
|
||||
<source>Forward up to 20 messages at once.</source>
|
||||
<target>Legfeljebb 20 üzenet egyszerre való továbbítása.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarded" xml:space="preserve">
|
||||
@@ -3615,7 +3623,7 @@ Hiba: %2$@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Hide" xml:space="preserve">
|
||||
<source>Hide</source>
|
||||
<target>Elrejtés</target>
|
||||
<target>Összecsukás</target>
|
||||
<note>chat item action</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Hide app screen in the recent apps." xml:space="preserve">
|
||||
@@ -3700,7 +3708,7 @@ Hiba: %2$@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="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)." xml:space="preserve">
|
||||
<source>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).</source>
|
||||
<target>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).</target>
|
||||
<target>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).</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Ignore" xml:space="preserve">
|
||||
@@ -3761,6 +3769,8 @@ Hiba: %2$@</target>
|
||||
<trans-unit id="Improved delivery, reduced traffic usage. More improvements are coming soon!" xml:space="preserve">
|
||||
<source>Improved delivery, reduced traffic usage.
|
||||
More improvements are coming soon!</source>
|
||||
<target>Továbbfejlesztett kézbesítés, csökkentett adatforgalom-használat.
|
||||
További fejlesztések hamarosan!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved message delivery" xml:space="preserve">
|
||||
@@ -4205,7 +4215,7 @@ Ez az Ön hivatkozása a(z) %@ csoporthoz!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*" xml:space="preserve">
|
||||
<source>Many people asked: *if SimpleX has no user identifiers, how can it deliver messages?*</source>
|
||||
<target>Sokan kérdezték: *ha a SimpleX Chatnek nincsenek felhasználói azonosítói, akkor hogyan tud üzeneteket kézbesíteni?*</target>
|
||||
<target>Sokan kérdezték: *ha a SimpleX Chatnek nincsenek felhasználó-azonosítói, akkor hogyan tud üzeneteket kézbesíteni?*</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Mark deleted for everyone" xml:space="preserve">
|
||||
@@ -4783,7 +4793,7 @@ VPN engedélyezése szükséges.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**." xml:space="preserve">
|
||||
<source>Only client devices store user profiles, contacts, groups, and messages sent with **2-layer end-to-end encryption**.</source>
|
||||
<target>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.</target>
|
||||
<target>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.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Only delete conversation" xml:space="preserve">
|
||||
@@ -4893,7 +4903,7 @@ VPN engedélyezése szükséges.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open user profiles" xml:space="preserve">
|
||||
<source>Open user profiles</source>
|
||||
<target>Felhasználói profilok megnyitása</target>
|
||||
<target>Felhasználó-profilok megnyitása</target>
|
||||
<note>authentication reason</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Open-source protocol and code – anybody can run the servers." xml:space="preserve">
|
||||
@@ -4945,12 +4955,12 @@ VPN engedélyezése szükséges.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING count" xml:space="preserve">
|
||||
<source>PING count</source>
|
||||
<target>PING számláló</target>
|
||||
<target>PING-ek száma</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="PING interval" xml:space="preserve">
|
||||
<source>PING interval</source>
|
||||
<target>PING időköze</target>
|
||||
<target>Időtartam a PING-ek között</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Passcode" xml:space="preserve">
|
||||
@@ -5306,12 +5316,12 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben.
|
||||
</trans-unit>
|
||||
<trans-unit id="Protocol timeout" xml:space="preserve">
|
||||
<source>Protocol timeout</source>
|
||||
<target>Protokoll időtúllépés</target>
|
||||
<target>Protokoll időtúllépése</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Protocol timeout per KB" xml:space="preserve">
|
||||
<source>Protocol timeout per KB</source>
|
||||
<target>Protokoll időkorlát KB-onként</target>
|
||||
<target>Protokoll időtúllépése KB-onként</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Proxied" xml:space="preserve">
|
||||
@@ -5436,7 +5446,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben.
|
||||
</trans-unit>
|
||||
<trans-unit id="Received total" xml:space="preserve">
|
||||
<source>Received total</source>
|
||||
<target>Összes fogadott</target>
|
||||
<target>Összes fogadott üzenet</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Receiving address will be changed to a different server. Address change will complete after sender comes online." xml:space="preserve">
|
||||
@@ -5808,7 +5818,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben.
|
||||
</trans-unit>
|
||||
<trans-unit id="Save profile password" xml:space="preserve">
|
||||
<source>Save profile password</source>
|
||||
<target>Felhasználói fiók jelszavának mentése</target>
|
||||
<target>Profiljelszó mentése</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Save servers" xml:space="preserve">
|
||||
@@ -6148,7 +6158,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben.
|
||||
</trans-unit>
|
||||
<trans-unit id="Sent total" xml:space="preserve">
|
||||
<source>Sent total</source>
|
||||
<target>Összes elküldött</target>
|
||||
<target>Összes elküldött üzenet</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Sent via proxy" xml:space="preserve">
|
||||
@@ -6363,7 +6373,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben.
|
||||
</trans-unit>
|
||||
<trans-unit id="Show message status" xml:space="preserve">
|
||||
<source>Show message status</source>
|
||||
<target>Üzenet állapot megjelenítése</target>
|
||||
<target>Üzenetállapot megjelenítése</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Show percentage" xml:space="preserve">
|
||||
@@ -6463,6 +6473,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben.
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
|
||||
<source>SimpleX protocols reviewed by Trail of Bits.</source>
|
||||
<target>A SimpleX Chat biztonsága a Trail of Bits által lett újraauditálva.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Simplified incognito mode" xml:space="preserve">
|
||||
@@ -6642,10 +6653,12 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben.
|
||||
</trans-unit>
|
||||
<trans-unit id="Switch audio and video during the call." xml:space="preserve">
|
||||
<source>Switch audio and video during the call.</source>
|
||||
<target>Hang/Videó váltása hívás közben.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Switch chat profile for 1-time invitations." xml:space="preserve">
|
||||
<source>Switch chat profile for 1-time invitations.</source>
|
||||
<target>Csevegési profilváltás az egyszer használható meghívókhoz.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="System" xml:space="preserve">
|
||||
@@ -6665,7 +6678,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben.
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP connection timeout" xml:space="preserve">
|
||||
<source>TCP connection timeout</source>
|
||||
<target>TCP kapcsolat időtúllépés</target>
|
||||
<target>TCP kapcsolat időtúllépése</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="TCP_KEEPCNT" xml:space="preserve">
|
||||
@@ -6700,7 +6713,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben.
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to Connect" xml:space="preserve">
|
||||
<source>Tap to Connect</source>
|
||||
<target>Koppintson a kapcsolódáshoz</target>
|
||||
<target>Koppintson ide a kapcsolódáshoz</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to activate profile." xml:space="preserve">
|
||||
@@ -6710,12 +6723,12 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben.
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to join" xml:space="preserve">
|
||||
<source>Tap to join</source>
|
||||
<target>Koppintson a csatlakozáshoz</target>
|
||||
<target>Koppintson ide a csatlakozáshoz</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to join incognito" xml:space="preserve">
|
||||
<source>Tap to join incognito</source>
|
||||
<target>Koppintson az inkognitóban való csatlakozáshoz</target>
|
||||
<target>Koppintson ide az inkognitóban való csatlakozáshoz</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to paste link" xml:space="preserve">
|
||||
@@ -6725,7 +6738,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben.
|
||||
</trans-unit>
|
||||
<trans-unit id="Tap to scan" xml:space="preserve">
|
||||
<source>Tap to scan</source>
|
||||
<target>Koppintson a beolvasáshoz</target>
|
||||
<target>Koppintson ide a QR-kód beolvasáshoz</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Temporary file error" xml:space="preserve">
|
||||
@@ -6770,7 +6783,7 @@ Engedélyezze a „Beállítások -> Hálózat és kiszolgálók” menüben.
|
||||
</trans-unit>
|
||||
<trans-unit id="The 1st platform without any user identifiers – private by design." xml:space="preserve">
|
||||
<source>The 1st platform without any user identifiers – private by design.</source>
|
||||
<target>Az első csevegési rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre.</target>
|
||||
<target>Az első csevegési rendszer bármiféle felhasználó-azonosító nélkül - privátra lett tervezre.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="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." xml:space="preserve">
|
||||
@@ -6992,7 +7005,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts." xml:space="preserve">
|
||||
<source>To protect privacy, instead of user IDs used by all other platforms, SimpleX has identifiers for message queues, separate for each of your contacts.</source>
|
||||
<target>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.</target>
|
||||
<target>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.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="To protect timezone, image/voice files use UTC." xml:space="preserve">
|
||||
@@ -7059,12 +7072,12 @@ A funkció bekapcsolása előtt a rendszer felszólítja a képernyőzár beáll
|
||||
</trans-unit>
|
||||
<trans-unit id="Total" xml:space="preserve">
|
||||
<source>Total</source>
|
||||
<target>Összesen</target>
|
||||
<target>Összes kapcsolat</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Transport isolation" xml:space="preserve">
|
||||
<source>Transport isolation</source>
|
||||
<target>Kapcsolat izolációs mód</target>
|
||||
<target>Átvitel-izoláció módja</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Transport sessions" xml:space="preserve">
|
||||
@@ -7728,7 +7741,7 @@ Csatlakozáskérés megismétlése?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can enable them later via app Privacy & Security settings." xml:space="preserve">
|
||||
<source>You can enable them later via app Privacy & Security settings.</source>
|
||||
<target>Később engedélyezheti őket az alkalmazás „Adatvédelem és biztonság” menüben.</target>
|
||||
<target>Később engedélyezheti őket az alkalmazás „Adatvédelem és biztonság” menüjében.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can give another try." xml:space="preserve">
|
||||
@@ -7738,7 +7751,7 @@ Csatlakozáskérés megismétlése?</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can hide or mute a user profile - swipe it to the right." xml:space="preserve">
|
||||
<source>You can hide or mute a user profile - swipe it to the right.</source>
|
||||
<target>Elrejtheti vagy lenémíthatja a felhasználó profiljait - csúsztassa jobbra a profilt.</target>
|
||||
<target>Elrejtheti vagy lenémíthatja a felhasználó -profiljait - csúsztassa jobbra a profilt.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="You can make it visible to your SimpleX contacts via Settings." xml:space="preserve">
|
||||
@@ -9068,7 +9081,7 @@ utoljára fogadott üzenet: %2$@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSCameraUsageDescription" xml:space="preserve">
|
||||
<source>SimpleX needs camera access to scan QR codes to connect to other users and for video calls.</source>
|
||||
<target>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.</target>
|
||||
<target>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.</target>
|
||||
<note>Privacy - Camera Usage Description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSFaceIDUsageDescription" xml:space="preserve">
|
||||
@@ -9083,12 +9096,12 @@ utoljára fogadott üzenet: %2$@</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSMicrophoneUsageDescription" xml:space="preserve">
|
||||
<source>SimpleX needs microphone access for audio and video calls, and to record voice messages.</source>
|
||||
<target>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.</target>
|
||||
<target>A SimpleXnek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez.</target>
|
||||
<note>Privacy - Microphone Usage Description</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="NSPhotoLibraryAddUsageDescription" xml:space="preserve">
|
||||
<source>SimpleX needs access to Photo Library for saving captured and received media</source>
|
||||
<target>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</target>
|
||||
<target>A SimpleXnek galéria-hozzáférésre van szüksége a rögzített és fogadott média mentéséhez</target>
|
||||
<note>Privacy - Photo Library Additions Usage Description</note>
|
||||
</trans-unit>
|
||||
</body>
|
||||
|
||||
@@ -1087,6 +1087,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better calls" xml:space="preserve">
|
||||
<source>Better calls</source>
|
||||
<target>Chiamate migliorate</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better groups" xml:space="preserve">
|
||||
@@ -1096,6 +1097,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better message dates." xml:space="preserve">
|
||||
<source>Better message dates.</source>
|
||||
<target>Date dei messaggi migliorate.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better messages" xml:space="preserve">
|
||||
@@ -1110,14 +1112,17 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better notifications" xml:space="preserve">
|
||||
<source>Better notifications</source>
|
||||
<target>Notifiche migliorate</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better security ✅" xml:space="preserve">
|
||||
<source>Better security ✅</source>
|
||||
<target>Sicurezza migliorata ✅</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better user experience" xml:space="preserve">
|
||||
<source>Better user experience</source>
|
||||
<target>Esperienza utente migliorata</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Black" xml:space="preserve">
|
||||
@@ -1942,6 +1947,7 @@ Questo è il tuo link una tantum!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Customizable message shape." xml:space="preserve">
|
||||
<source>Customizable message shape.</source>
|
||||
<target>Forma dei messaggi personalizzabile.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Customize theme" xml:space="preserve">
|
||||
@@ -2240,6 +2246,7 @@ Questo è il tuo link una tantum!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete or moderate up to 200 messages." xml:space="preserve">
|
||||
<source>Delete or moderate up to 200 messages.</source>
|
||||
<target>Elimina o modera fino a 200 messaggi.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete pending connection?" xml:space="preserve">
|
||||
@@ -3367,6 +3374,7 @@ Questo è il tuo link una tantum!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forward up to 20 messages at once." xml:space="preserve">
|
||||
<source>Forward up to 20 messages at once.</source>
|
||||
<target>Inoltra fino a 20 messaggi alla volta.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarded" xml:space="preserve">
|
||||
@@ -3761,6 +3769,8 @@ Errore: %2$@</target>
|
||||
<trans-unit id="Improved delivery, reduced traffic usage. More improvements are coming soon!" xml:space="preserve">
|
||||
<source>Improved delivery, reduced traffic usage.
|
||||
More improvements are coming soon!</source>
|
||||
<target>Consegna migliorata, utilizzo di traffico ridotto.
|
||||
Altri miglioramenti sono in arrivo!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved message delivery" xml:space="preserve">
|
||||
@@ -6463,6 +6473,7 @@ Attivalo nelle impostazioni *Rete e server*.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
|
||||
<source>SimpleX protocols reviewed by Trail of Bits.</source>
|
||||
<target>Protocolli di SimpleX esaminati da Trail of Bits.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Simplified incognito mode" xml:space="preserve">
|
||||
@@ -6642,10 +6653,12 @@ Attivalo nelle impostazioni *Rete e server*.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Switch audio and video during the call." xml:space="preserve">
|
||||
<source>Switch audio and video during the call.</source>
|
||||
<target>Cambia tra audio e video durante la chiamata.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Switch chat profile for 1-time invitations." xml:space="preserve">
|
||||
<source>Switch chat profile for 1-time invitations.</source>
|
||||
<target>Cambia profilo di chat per inviti una tantum.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="System" xml:space="preserve">
|
||||
|
||||
@@ -4121,10 +4121,12 @@ This is your link for group %@!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." xml:space="preserve">
|
||||
<source>Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.</source>
|
||||
<target>メッセージ、ファイル、通話は、前方秘匿性、否認可能性および侵入復元性を備えた**エンドツーエンドの暗号化**によって保護されます。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." xml:space="preserve">
|
||||
<source>Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery.</source>
|
||||
<target>メッセージ、ファイル、通話は、前方秘匿性、否認可能性および侵入復元性を備えた**耐量子E2E暗号化**によって保護されます。</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Migrate device" xml:space="preserve">
|
||||
|
||||
@@ -1087,6 +1087,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better calls" xml:space="preserve">
|
||||
<source>Better calls</source>
|
||||
<target>Betere gesprekken</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better groups" xml:space="preserve">
|
||||
@@ -1096,6 +1097,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better message dates." xml:space="preserve">
|
||||
<source>Better message dates.</source>
|
||||
<target>Betere datums voor berichten.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better messages" xml:space="preserve">
|
||||
@@ -1110,14 +1112,17 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better notifications" xml:space="preserve">
|
||||
<source>Better notifications</source>
|
||||
<target>Betere meldingen</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better security ✅" xml:space="preserve">
|
||||
<source>Better security ✅</source>
|
||||
<target>Betere beveiliging ✅</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better user experience" xml:space="preserve">
|
||||
<source>Better user experience</source>
|
||||
<target>Betere gebruikerservaring</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Black" xml:space="preserve">
|
||||
@@ -1942,6 +1947,7 @@ Dit is uw eigen eenmalige link!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Customizable message shape." xml:space="preserve">
|
||||
<source>Customizable message shape.</source>
|
||||
<target>Aanpasbare berichtvorm.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Customize theme" xml:space="preserve">
|
||||
@@ -2240,6 +2246,7 @@ Dit is uw eigen eenmalige link!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete or moderate up to 200 messages." xml:space="preserve">
|
||||
<source>Delete or moderate up to 200 messages.</source>
|
||||
<target>Maximaal 200 berichten verwijderen of modereren.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete pending connection?" xml:space="preserve">
|
||||
@@ -3367,6 +3374,7 @@ Dit is uw eigen eenmalige link!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forward up to 20 messages at once." xml:space="preserve">
|
||||
<source>Forward up to 20 messages at once.</source>
|
||||
<target>Stuur maximaal 20 berichten tegelijk door.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarded" xml:space="preserve">
|
||||
@@ -3761,6 +3769,8 @@ Fout: %2$@</target>
|
||||
<trans-unit id="Improved delivery, reduced traffic usage. More improvements are coming soon!" xml:space="preserve">
|
||||
<source>Improved delivery, reduced traffic usage.
|
||||
More improvements are coming soon!</source>
|
||||
<target>Verbeterde levering, minder data gebruik.
|
||||
Binnenkort meer verbeteringen!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved message delivery" xml:space="preserve">
|
||||
@@ -6463,6 +6473,7 @@ Schakel dit in in *Netwerk en servers*-instellingen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
|
||||
<source>SimpleX protocols reviewed by Trail of Bits.</source>
|
||||
<target>SimpleX-protocollen beoordeeld door Trail of Bits.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Simplified incognito mode" xml:space="preserve">
|
||||
@@ -6642,10 +6653,12 @@ Schakel dit in in *Netwerk en servers*-instellingen.</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Switch audio and video during the call." xml:space="preserve">
|
||||
<source>Switch audio and video during the call.</source>
|
||||
<target>Wisselen tussen audio en video tijdens het gesprek.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Switch chat profile for 1-time invitations." xml:space="preserve">
|
||||
<source>Switch chat profile for 1-time invitations.</source>
|
||||
<target>Wijzig chatprofiel voor eenmalige uitnodigingen.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="System" xml:space="preserve">
|
||||
|
||||
@@ -139,6 +139,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@" xml:space="preserve">
|
||||
<source>%1$@, %2$@</source>
|
||||
<target>%1$@, %2$@</target>
|
||||
<note>format for date separator in chat</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="%@, %@ and %lld members" xml:space="preserve">
|
||||
@@ -1086,6 +1087,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better calls" xml:space="preserve">
|
||||
<source>Better calls</source>
|
||||
<target>Улучшенные звонки</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better groups" xml:space="preserve">
|
||||
@@ -1095,6 +1097,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better message dates." xml:space="preserve">
|
||||
<source>Better message dates.</source>
|
||||
<target>Улучшенные даты сообщений.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better messages" xml:space="preserve">
|
||||
@@ -1109,14 +1112,17 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better notifications" xml:space="preserve">
|
||||
<source>Better notifications</source>
|
||||
<target>Улучшенные уведомления</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better security ✅" xml:space="preserve">
|
||||
<source>Better security ✅</source>
|
||||
<target>Улучшенная безопасность ✅</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better user experience" xml:space="preserve">
|
||||
<source>Better user experience</source>
|
||||
<target>Улучшенный интерфейс</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Black" xml:space="preserve">
|
||||
@@ -1941,6 +1947,7 @@ This is your own one-time link!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Customizable message shape." xml:space="preserve">
|
||||
<source>Customizable message shape.</source>
|
||||
<target>Настраиваемая форма сообщений.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Customize theme" xml:space="preserve">
|
||||
@@ -2239,6 +2246,7 @@ This is your own one-time link!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete or moderate up to 200 messages." xml:space="preserve">
|
||||
<source>Delete or moderate up to 200 messages.</source>
|
||||
<target>Удаляйте или модерируйте до 200 сообщений.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete pending connection?" xml:space="preserve">
|
||||
@@ -3366,6 +3374,7 @@ This is your own one-time link!</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forward up to 20 messages at once." xml:space="preserve">
|
||||
<source>Forward up to 20 messages at once.</source>
|
||||
<target>Пересылайте до 20 сообщений за раз.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarded" xml:space="preserve">
|
||||
@@ -3760,6 +3769,7 @@ Error: %2$@</source>
|
||||
<trans-unit id="Improved delivery, reduced traffic usage. More improvements are coming soon!" xml:space="preserve">
|
||||
<source>Improved delivery, reduced traffic usage.
|
||||
More improvements are coming soon!</source>
|
||||
<target>Улучшенная доставка, меньше трафик.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved message delivery" xml:space="preserve">
|
||||
@@ -6462,6 +6472,7 @@ Enable in *Network & servers* settings.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
|
||||
<source>SimpleX protocols reviewed by Trail of Bits.</source>
|
||||
<target>Аудит SimpleX протоколов от Trail of Bits.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Simplified incognito mode" xml:space="preserve">
|
||||
@@ -6641,10 +6652,12 @@ Enable in *Network & servers* settings.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Switch audio and video during the call." xml:space="preserve">
|
||||
<source>Switch audio and video during the call.</source>
|
||||
<target>Переключайте звук и видео во время звонка.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Switch chat profile for 1-time invitations." xml:space="preserve">
|
||||
<source>Switch chat profile for 1-time invitations.</source>
|
||||
<target>Переключайте профиль чата для одноразовых приглашений.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="System" xml:space="preserve">
|
||||
|
||||
@@ -1087,6 +1087,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better calls" xml:space="preserve">
|
||||
<source>Better calls</source>
|
||||
<target>Daha iyi aramalar</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better groups" xml:space="preserve">
|
||||
@@ -1096,6 +1097,7 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better message dates." xml:space="preserve">
|
||||
<source>Better message dates.</source>
|
||||
<target>Daha iyi mesaj tarihleri.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better messages" xml:space="preserve">
|
||||
@@ -1110,14 +1112,17 @@
|
||||
</trans-unit>
|
||||
<trans-unit id="Better notifications" xml:space="preserve">
|
||||
<source>Better notifications</source>
|
||||
<target>Daha iyi bildirimler</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better security ✅" xml:space="preserve">
|
||||
<source>Better security ✅</source>
|
||||
<target>Daha iyi güvenlik ✅</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Better user experience" xml:space="preserve">
|
||||
<source>Better user experience</source>
|
||||
<target>Daha iyi kullanıcı deneyimi</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Black" xml:space="preserve">
|
||||
@@ -1942,6 +1947,7 @@ Bu senin kendi tek kullanımlık bağlantın!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Customizable message shape." xml:space="preserve">
|
||||
<source>Customizable message shape.</source>
|
||||
<target>Özelleştirilebilir mesaj şekli.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Customize theme" xml:space="preserve">
|
||||
@@ -2240,6 +2246,7 @@ Bu senin kendi tek kullanımlık bağlantın!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete or moderate up to 200 messages." xml:space="preserve">
|
||||
<source>Delete or moderate up to 200 messages.</source>
|
||||
<target>200'e kadar mesajı silin veya düzenleyin.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Delete pending connection?" xml:space="preserve">
|
||||
@@ -3367,6 +3374,7 @@ Bu senin kendi tek kullanımlık bağlantın!</target>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forward up to 20 messages at once." xml:space="preserve">
|
||||
<source>Forward up to 20 messages at once.</source>
|
||||
<target>Aynı anda en fazla 20 mesaj iletin.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Forwarded" xml:space="preserve">
|
||||
@@ -3761,6 +3769,8 @@ Hata: %2$@</target>
|
||||
<trans-unit id="Improved delivery, reduced traffic usage. More improvements are coming soon!" xml:space="preserve">
|
||||
<source>Improved delivery, reduced traffic usage.
|
||||
More improvements are coming soon!</source>
|
||||
<target>İyileştirilmiş teslimat, azaltılmış trafik kullanımı.
|
||||
Daha fazla iyileştirme yakında geliyor!</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Improved message delivery" xml:space="preserve">
|
||||
@@ -6463,6 +6473,7 @@ Enable in *Network & servers* settings.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="SimpleX protocols reviewed by Trail of Bits." xml:space="preserve">
|
||||
<source>SimpleX protocols reviewed by Trail of Bits.</source>
|
||||
<target>SimpleX protokolleri Trail of Bits tarafından incelenmiştir.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Simplified incognito mode" xml:space="preserve">
|
||||
@@ -6642,10 +6653,12 @@ Enable in *Network & servers* settings.</source>
|
||||
</trans-unit>
|
||||
<trans-unit id="Switch audio and video during the call." xml:space="preserve">
|
||||
<source>Switch audio and video during the call.</source>
|
||||
<target>Görüşme sırasında ses ve görüntüyü değiştirin.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="Switch chat profile for 1-time invitations." xml:space="preserve">
|
||||
<source>Switch chat profile for 1-time invitations.</source>
|
||||
<target>Sohbet profilini 1 kerelik davetler için değiştirin.</target>
|
||||
<note>No comment provided by engineer.</note>
|
||||
</trans-unit>
|
||||
<trans-unit id="System" xml:space="preserve">
|
||||
|
||||
@@ -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<UUID> = []
|
||||
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<String, ExpectedMessage> = [:] // 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<ChatId>()
|
||||
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
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -144,15 +144,15 @@
|
||||
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 */; };
|
||||
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 */; };
|
||||
@@ -209,6 +209,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 */; };
|
||||
@@ -486,15 +487,15 @@
|
||||
5CFE0920282EEAF60002594B /* ZoomableScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; name = ZoomableScrollView.swift; path = Shared/Views/ZoomableScrollView.swift; sourceTree = SOURCE_ROOT; };
|
||||
640417CB2B29B8C200CCB412 /* NewChatMenuButton.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatMenuButton.swift; sourceTree = "<group>"; };
|
||||
640417CC2B29B8C200CCB412 /* NewChatView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = NewChatView.swift; sourceTree = "<group>"; };
|
||||
6405489E2CB56735005DE1E4 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = "<group>"; };
|
||||
6405489F2CB56735005DE1E4 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = "<group>"; };
|
||||
640548A02CB56735005DE1E4 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
640548A22CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv.a"; sourceTree = "<group>"; };
|
||||
6407BA82295DA85D0082BA18 /* CIInvalidJSONView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIInvalidJSONView.swift; sourceTree = "<group>"; };
|
||||
6419EC552AB8BC8B004A607A /* ContextInvitingContactMemberView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContextInvitingContactMemberView.swift; sourceTree = "<group>"; };
|
||||
6419EC572AB97507004A607A /* CIMemberCreatedContactView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIMemberCreatedContactView.swift; sourceTree = "<group>"; };
|
||||
6432857B2925443C00FBE5C8 /* GroupPreferencesView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = GroupPreferencesView.swift; sourceTree = "<group>"; };
|
||||
643B3B402CCBEB080083A2CF /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmpxx.a; path = Libraries/libgmpxx.a; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
643B3B422CCBEB080083A2CF /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libffi.a; path = Libraries/libffi.a; sourceTree = "<group>"; };
|
||||
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 = "<group>"; };
|
||||
643B3B442CCBEB080083A2CF /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; name = libgmp.a; path = Libraries/libgmp.a; sourceTree = "<group>"; };
|
||||
6440C9FF288857A10062C672 /* CIEventView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIEventView.swift; sourceTree = "<group>"; };
|
||||
6440CA02288AECA70062C672 /* AddGroupMembersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupMembersView.swift; sourceTree = "<group>"; };
|
||||
6442E0B9287F169300CEC0F9 /* AddGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AddGroupView.swift; sourceTree = "<group>"; };
|
||||
@@ -550,6 +551,7 @@
|
||||
CE3097FA2C4C0C9F00180898 /* ErrorAlert.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ErrorAlert.swift; sourceTree = "<group>"; };
|
||||
CE7548092C622630009579B7 /* SwipeLabel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SwipeLabel.swift; sourceTree = "<group>"; };
|
||||
CE984D4A2C36C5D500E3AEFF /* ChatItemClipShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatItemClipShape.swift; sourceTree = "<group>"; };
|
||||
CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UserDefault.swift; sourceTree = "<group>"; };
|
||||
CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StickyScrollView.swift; sourceTree = "<group>"; };
|
||||
CEDE70212C48FD9500233B1F /* SEChatState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SEChatState.swift; sourceTree = "<group>"; };
|
||||
CEE723A72C3BD3D70009AE93 /* SimpleX SE.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = "SimpleX SE.appex"; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -655,14 +657,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 */,
|
||||
643B3B452CCBEB080083A2CF /* libgmpxx.a in Frameworks */,
|
||||
643B3B472CCBEB080083A2CF /* libffi.a in Frameworks */,
|
||||
643B3B492CCBEB080083A2CF /* libgmp.a in Frameworks */,
|
||||
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
|
||||
640548A62CB56735005DE1E4 /* libHSsimplex-chat-6.1.0.7-EtclnBf7vnkLnhnDA1lixv-ghc9.6.3.a 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 */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -739,11 +741,6 @@
|
||||
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 */,
|
||||
);
|
||||
path = Libraries;
|
||||
sourceTree = "<group>";
|
||||
@@ -802,6 +799,7 @@
|
||||
CE176F1F2C87014C00145DBC /* InvertedForegroundStyle.swift */,
|
||||
CEDB245A2C9CD71800FBC5F6 /* StickyScrollView.swift */,
|
||||
CEFB2EDE2CA1BCC7004B1ECE /* SheetRepresentable.swift */,
|
||||
CEA6E91B2CBD21B0002B5DB4 /* UserDefault.swift */,
|
||||
);
|
||||
path = Helpers;
|
||||
sourceTree = "<group>";
|
||||
@@ -814,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 */,
|
||||
@@ -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 */,
|
||||
@@ -1899,7 +1903,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 242;
|
||||
CURRENT_PROJECT_VERSION = 245;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -1924,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;
|
||||
@@ -1948,7 +1952,7 @@
|
||||
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
|
||||
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 242;
|
||||
CURRENT_PROJECT_VERSION = 245;
|
||||
DEAD_CODE_STRIPPING = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
@@ -1973,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;
|
||||
@@ -1989,11 +1993,11 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 242;
|
||||
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;
|
||||
@@ -2009,11 +2013,11 @@
|
||||
buildSettings = {
|
||||
ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES;
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 242;
|
||||
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;
|
||||
@@ -2034,7 +2038,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 = 245;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
GCC_OPTIMIZATION_LEVEL = s;
|
||||
@@ -2049,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 = "";
|
||||
@@ -2071,7 +2075,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 = 245;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_BITCODE = NO;
|
||||
ENABLE_CODE_COVERAGE = NO;
|
||||
@@ -2086,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 = "";
|
||||
@@ -2108,7 +2112,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 = 245;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2134,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;
|
||||
@@ -2159,7 +2163,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 = 245;
|
||||
DEFINES_MODULE = YES;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
DYLIB_COMPATIBILITY_VERSION = 1;
|
||||
@@ -2185,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;
|
||||
@@ -2210,7 +2214,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 = 245;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -2225,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;
|
||||
@@ -2244,7 +2248,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 = 245;
|
||||
DEVELOPMENT_TEAM = 5NN7GUYB6T;
|
||||
ENABLE_USER_SCRIPT_SANDBOXING = YES;
|
||||
GCC_C_LANGUAGE_STANDARD = gnu17;
|
||||
@@ -2259,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;
|
||||
|
||||
@@ -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))
|
||||
@@ -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)
|
||||
@@ -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
|
||||
|
||||
@@ -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,
|
||||
|
||||
@@ -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
|
||||
@@ -2268,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 {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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." = "Borra o modera hasta 200 mensajes a la vez.";
|
||||
|
||||
/* 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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
@@ -933,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";
|
||||
@@ -1140,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";
|
||||
@@ -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";
|
||||
|
||||
@@ -1323,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ó.";
|
||||
@@ -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?";
|
||||
|
||||
@@ -1555,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";
|
||||
@@ -1579,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";
|
||||
@@ -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";
|
||||
@@ -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.";
|
||||
@@ -3361,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.";
|
||||
@@ -3523,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";
|
||||
@@ -3610,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.";
|
||||
@@ -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";
|
||||
@@ -4074,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";
|
||||
@@ -4212,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";
|
||||
@@ -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,25 +4446,25 @@
|
||||
"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";
|
||||
|
||||
/* 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";
|
||||
@@ -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.";
|
||||
@@ -4638,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";
|
||||
@@ -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”.";
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
@@ -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…" = "データベースのアーカイブを移行しています…";
|
||||
|
||||
|
||||
@@ -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." = "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.";
|
||||
|
||||
/* No comment provided by engineer. */
|
||||
"System" = "Systeem";
|
||||
|
||||
|
||||
@@ -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" = "Системная";
|
||||
|
||||
|
||||
@@ -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";
|
||||
|
||||
|
||||
+7
-3
@@ -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()
|
||||
|
||||
+1
-2
@@ -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)
|
||||
}
|
||||
|
||||
+19
-2
@@ -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.*
|
||||
@@ -192,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))
|
||||
@@ -702,9 +708,10 @@ fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, 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 +782,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<WebView?>, private val assetLoader: WebViewAssetLoader) : WebViewClientCompat() {
|
||||
override fun shouldInterceptRequest(
|
||||
view: WebView,
|
||||
|
||||
@@ -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
|
||||
|
||||
+11
@@ -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)
|
||||
@@ -6045,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"
|
||||
@@ -6057,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()
|
||||
|
||||
@@ -144,6 +144,8 @@
|
||||
<string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Please check that you used the correct link or ask your contact to send you another one.</string>
|
||||
<string name="connection_error_auth">Connection error (AUTH)</string>
|
||||
<string name="connection_error_auth_desc">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.</string>
|
||||
<string name="connection_error_quota">Undelivered messages</string>
|
||||
<string name="connection_error_quota_desc">The connection reached the limit of undelivered messages, your contact may be offline.</string>
|
||||
<string name="error_accepting_contact_request">Error accepting contact request</string>
|
||||
<string name="sender_may_have_deleted_the_connection_request">Sender may have deleted the connection request.</string>
|
||||
<string name="error_deleting_contact">Error deleting contact</string>
|
||||
|
||||
@@ -667,8 +667,8 @@
|
||||
<string name="callstate_received_confirmation">obdržel potvrzení…</string>
|
||||
<string name="callstate_connecting">připojování…</string>
|
||||
<string name="privacy_redefined">Nové vymezení soukromí</string>
|
||||
<string name="first_platform_without_user_ids">1. platforma bez jakýchkoliv uživatelských identifikátorů – soukromá již od návrhu.</string>
|
||||
<string name="immune_to_spam_and_abuse">Odolná vůči spamu a zneužití</string>
|
||||
<string name="first_platform_without_user_ids">Bez uživatelských identifikátorů</string>
|
||||
<string name="immune_to_spam_and_abuse">Odolná vůči spamu</string>
|
||||
<string name="to_protect_privacy_simplex_has_ids_for_queues">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ů.</string>
|
||||
<string name="many_people_asked_how_can_it_deliver"><![CDATA[Mnoho lidí se ptá: <i>když SimpleX nemá žádný identifikátor uživatelů, jak může doručovat zprávy\?</i>]]></string>
|
||||
<string name="you_control_servers_to_receive_your_contacts_to_send"><![CDATA[Vy určujete, přes které servery <b>přijímat</b> zprávy, vaše kontakty – servery, které používáte k zasílání zpráv.]]></string>
|
||||
@@ -679,8 +679,8 @@
|
||||
<string name="onboarding_notifications_mode_off">Když aplikace běží</string>
|
||||
<string name="onboarding_notifications_mode_service">Okamžité</string>
|
||||
<string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>Nejlepší pro baterii</b>. Budete přijímat oznámení pouze když aplikace běží (žádná služba na pozadí).]]></string>
|
||||
<string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b>Dobré pro baterii</b>. Služba na pozadí bude kontrolovat každých 10 minut. Můžete zmeškat hovory nebo naléhavé zprávy.]]></string>
|
||||
<string name="onboarding_notifications_mode_service_desc"><![CDATA[<b>Využívá více baterie</b>! Služba na pozadí je spuštěna vždy - oznámení se zobrazí, jakmile jsou zprávy k dispozici.]]></string>
|
||||
<string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b>Dobré pro baterii</b>. Apka bude kontrolovat zprávy každých 10 minut. Můžete zmeškat volání nebo naléhavé zprávy.]]></string>
|
||||
<string name="onboarding_notifications_mode_service_desc"><![CDATA[<b>Využívá více baterie</b>! Apka stále běží na pozadí - oznámení se zobrazí okamžitě.]]></string>
|
||||
<string name="paste_the_link_you_received">Vložte přijatý odkaz</string>
|
||||
<string name="incoming_video_call">Příchozí videohovor</string>
|
||||
<string name="incoming_audio_call">Příchozí zvukový hovor</string>
|
||||
@@ -1582,7 +1582,7 @@
|
||||
<string name="profile_update_event_member_name_changed">člen %1$s změněn na %2$s</string>
|
||||
<string name="member_info_member_blocked">blokováno</string>
|
||||
<string name="member_blocked_by_admin">Blokováno adminem</string>
|
||||
<string name="share_text_created_at">Vytvořeno v: %s</string>
|
||||
<string name="share_text_created_at">Vytvořen v: %s</string>
|
||||
<string name="message_too_large">Zpráva příliš velká</string>
|
||||
<string name="remote_host_disconnected_from"><![CDATA[Odpojeno z mobilu <b>%s</b> z důvodu: %s]]></string>
|
||||
<string name="remote_host_was_disconnected_title">Spojení zastaveno</string>
|
||||
@@ -1817,8 +1817,7 @@
|
||||
<string name="v5_8_safe_files">Bezpečné přijímání souborů</string>
|
||||
<string name="v5_8_chat_themes">Nové motivy chatu</string>
|
||||
<string name="v5_8_private_routing">Soukromé směrování zpráv 🚀</string>
|
||||
<string name="v5_8_private_routing_descr">Chraňte vaši IP adresu před relé zpráv, které jste si vybrali.
|
||||
\nPovolit v nastavení *Síť & servery*.</string>
|
||||
<string name="v5_8_private_routing_descr">Chraňte vaši IP adresu před relé zpráv vašich kontaktů.\nPovolte v nastavení *Síť & servery*.</string>
|
||||
<string name="v5_8_message_delivery">Vylepšené doručování zpráv</string>
|
||||
<string name="v5_8_persian_ui">Perské UI</string>
|
||||
<string name="remote_ctrl_connection_stopped_desc">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 @@
|
||||
<string name="cant_call_member_alert_title">Nelze zavolat člena skupiny</string>
|
||||
<string name="deleted_chats">Archivované kontakty</string>
|
||||
<string name="v6_0_your_contacts_descr">Archivujte kontakty pro pozdější chatování.</string>
|
||||
<string name="smp_proxy_error_broker_host">Adresa předávacího serveru je nekompatibilní s nastavením sítě: %1$s.</string>
|
||||
<string name="smp_proxy_error_broker_version">Verze předávacího serveru je nekompatibilní s nastavením sítě: %1$s.</string>
|
||||
<string name="proxy_destination_error_broker_host">Cílová adresa serveru %1$s je nekompatibilní s nastavením přeposílajícího serveru %2$s.</string>
|
||||
<string name="smp_proxy_error_connecting">Chyba připojení k přeposílajícímu serveru %1$s. Prosím, zkuste to později.</string>
|
||||
<string name="proxy_destination_error_failed_to_connect">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.</string>
|
||||
<string name="cannot_share_message_alert_text">Vybrané nastavení chatu zakazuje tuto zprávu.</string>
|
||||
<string name="smp_servers_other">Jiné SMP servery</string>
|
||||
<string name="smp_servers_configured">Nastavené SMP servery</string>
|
||||
<string name="servers_info_subscriptions_connections_pending">Probíhá</string>
|
||||
<string name="chunks_uploaded">Části nahrány</string>
|
||||
<string name="n_file_errors">%1$d chuba souboru(ů):\n%2$s</string>
|
||||
<string name="n_other_file_errors">%1$d jiná chyba souboru(ů).</string>
|
||||
<string name="error_forwarding_messages">Chyba přeposílaní zpráv</string>
|
||||
<string name="srv_error_host">Adresa serveru není kompatibilní s nastavením sítě.</string>
|
||||
<string name="forward_alert_title_messages_to_forward">Předat %1$s zpráv(u)?</string>
|
||||
<string name="forward_alert_title_nothing_to_forward">Nic k předání!</string>
|
||||
<string name="forward_alert_forward_messages_without_files">Předat zprávy bez souborů?</string>
|
||||
<string name="forward_files_failed_to_receive_desc">%1$d soubor(y) se nepodařilo stáhnout.</string>
|
||||
<string name="forward_files_not_accepted_desc">%1$d soubor(y) nestažen(y).</string>
|
||||
<string name="forward_files_missing_desc">%1$d soubor(y) smazán(y).</string>
|
||||
<string name="forward_files_messages_deleted_after_selection_title">%1$s zprávy nepředány</string>
|
||||
<string name="forward_files_not_accepted_receive_files">Stáhnout</string>
|
||||
<string name="compose_forward_messages_n">Předávám %1$s zpráv</string>
|
||||
<string name="forward_multiple">Předat zprávy…</string>
|
||||
<string name="compose_save_messages_n">Uložit %1$s zpráv</string>
|
||||
<string name="network_proxy_auth_mode_no_auth">Nepoužívat autorizaci s proxy.</string>
|
||||
<string name="network_proxy_incorrect_config_title">Chyba ukládání proxy</string>
|
||||
<string name="network_proxy_incorrect_config_desc">Ujistěte se, že nastavení proxy je správné.</string>
|
||||
<string name="network_proxy_password">Heslo</string>
|
||||
<string name="network_proxy_auth">Proxy autentizace</string>
|
||||
<string name="app_check_for_updates_button_install">Instalovat aktualizace</string>
|
||||
<string name="app_check_for_updates_button_open">Otevřít umístění souboru</string>
|
||||
<string name="app_check_for_updates_download_started">Stahování aktualizace, nezavírejte aplikaci</string>
|
||||
<string name="app_check_for_updates_installed_successfully_desc">Prosím restartujte aplikaci.</string>
|
||||
<string name="app_check_for_updates_installed_successfully_title">Instalovány úspěšně</string>
|
||||
<string name="app_check_for_updates_button_remind_later">Připomenout později</string>
|
||||
<string name="app_check_for_updates_notice_title">Zkontrolovat aktualizace</string>
|
||||
<string name="privacy_media_blur_radius_off">Vypnuto</string>
|
||||
<string name="settings_section_title_chat_database">CHAT DATABÁZE</string>
|
||||
<string name="member_info_member_disabled">vypnut</string>
|
||||
<string name="message_queue_info_server_info">info fronty serveru: %1$s\n\nposlední obdržená zpráva: %2$s</string>
|
||||
<string name="network_options_save_and_reconnect">Uložit a připojit znovu</string>
|
||||
<string name="v6_0_chat_list_media">Hrajte ze seznamu chatů.</string>
|
||||
<string name="servers_info_messages_received">Přijatých zprávy</string>
|
||||
<string name="servers_info_reconnect_servers_title">Znovu připojit servery?</string>
|
||||
<string name="completed">Kompletní</string>
|
||||
<string name="secured">Zabezpečeno</string>
|
||||
<string name="error_parsing_uri_desc">Prosím zkontrolujte, že SimpleX odkaz je správný.</string>
|
||||
<string name="error_parsing_uri_title">Chybný odkaz</string>
|
||||
<string name="proxy_destination_error_broker_version">Verze cílového serveru %1$s je nekompatibilní s nastavením přeposílajícího serveru %2$s.</string>
|
||||
<string name="message_forwarded_title">Zpráva předána</string>
|
||||
<string name="keep_conversation">Udržujte konverzaci</string>
|
||||
<string name="only_delete_conversation">Jen smazat konverzaci</string>
|
||||
<string name="confirm_delete_contact_question">Potvrdit smazání kontaktu?</string>
|
||||
<string name="delete_contact_cannot_undo_warning">Kontakt bude smazán - nelze vrátit!</string>
|
||||
<string name="conversation_deleted">Konverzace odstraněna!</string>
|
||||
<string name="paste_link">Vložit odkaz</string>
|
||||
<string name="chat_database_exported_title">Chat databáze exportována</string>
|
||||
<string name="cant_send_message_to_member_alert_title">Členu skupiny nelze odeslat zprávu</string>
|
||||
<string name="calls_prohibited_ask_to_enable_calls_alert_text">Požádejte váš kontakt ať povolí volání.</string>
|
||||
<string name="color_sent_quote">Odeslaných odpovědí</string>
|
||||
<string name="wallpaper_scale">Škálovat</string>
|
||||
<string name="wallpaper_scale_fit">Přizpůsobit</string>
|
||||
<string name="v6_0_privacy_blur">Rozmazání pro lepší soukromí.</string>
|
||||
<string name="v6_0_connect_faster_descr">Připojte se k vašim přátelům rychleji.</string>
|
||||
<string name="v6_0_delete_many_messages_descr">Smazat až 20 zpráv najednou.</string>
|
||||
<string name="v6_0_increase_font_size">Zvětšit velikost písma.</string>
|
||||
<string name="v6_0_connection_servers_status_descr">Stav připojení a serverů.</string>
|
||||
<string name="v6_0_connection_servers_status">Kontrolujte svou síť</string>
|
||||
<string name="servers_info_sessions_errors">Chyby</string>
|
||||
<string name="servers_info_sessions_connected">Připojen</string>
|
||||
<string name="servers_info_sessions_connecting">Připojování</string>
|
||||
<string name="servers_info_connected_servers_section_header">Připojené servery</string>
|
||||
<string name="servers_info_previously_connected_servers_section_header">Dříve připojené servery</string>
|
||||
<string name="acknowledged">Potvrzeno</string>
|
||||
<string name="duplicates_label">duplikáty</string>
|
||||
<string name="deleted">Smazán</string>
|
||||
<string name="open_server_settings_button">Otevřít nastavení serveru</string>
|
||||
<string name="v6_0_new_chat_experience">Nový zážitek z chatu 🎉</string>
|
||||
<string name="v6_0_new_media_options">Nové možnosti médií</string>
|
||||
<string name="new_message">Nová zpráva</string>
|
||||
<string name="scan_paste_link">Skenovat / Vložit odkaz</string>
|
||||
<string name="no_filtered_contacts">Žádné filtrované kontakty</string>
|
||||
<string name="error_initializing_web_view_wrong_arch">Chyba inicializace WebView. Ujistěte se, že máte nainstalován WebView podporující architekturu arm64.\nChyba: %s</string>
|
||||
<string name="v6_0_private_routing_descr">Chrání vaši IP adresu a připojení.</string>
|
||||
<string name="forward_files_messages_deleted_after_selection_desc">Zprávy byly odstraněny poté, co jste je vybrali.</string>
|
||||
<string name="network_session_mode_session_description">Nové přihlašovací údaje SOCKS budou použity pokaždé, když zapnete aplikaci.</string>
|
||||
<string name="network_session_mode_server_description">Nové přihlašovací údaje SOCKS budou použity pro každý server.</string>
|
||||
<string name="servers_info_reconnect_servers_message">Znovu připojte všechny připojené servery pro vynucení doručení. Využívá další provoz.</string>
|
||||
<string name="reset_all_hints">Resetovat všechny tipy</string>
|
||||
<string name="forward_files_in_progress_desc">%1$d soubor(y) stále stahuji.</string>
|
||||
<string name="v6_1_message_dates_descr">Lepší datování zpráv.</string>
|
||||
<string name="v6_1_better_security">Lepší zabezpečení ✅</string>
|
||||
<string name="chunks_deleted">Části odstraněny</string>
|
||||
<string name="info_view_connect_button">připojení</string>
|
||||
<string name="current_user">Aktuální profil</string>
|
||||
<string name="servers_info_reconnect_server_error">Chyba znovu připojení serveru</string>
|
||||
<string name="servers_info_reconnect_servers_error">Chyba při opětovném připojování serverů</string>
|
||||
<string name="servers_info_reconnect_server_message">Znovu připojte server pro vynucení doručení. Využívá další provoz.</string>
|
||||
<string name="servers_info_modal_error_title">Chyba</string>
|
||||
<string name="delete_messages_cannot_be_undone_warning">Zprávy budou smazány - nelze vrátit!</string>
|
||||
<string name="delete_without_notification">Smazat bez upozornění</string>
|
||||
<string name="info_view_search_button">hledat</string>
|
||||
<string name="switching_profile_error_title">Chyba přepínání profilu</string>
|
||||
<string name="select_chat_profile">Vyberte chat profil</string>
|
||||
<string name="info_view_message_button">zpráva</string>
|
||||
<string name="info_view_open_button">otevřít</string>
|
||||
<string name="contact_deleted">Kontakt smazán!</string>
|
||||
<string name="member_info_member_inactive">neaktivní</string>
|
||||
<string name="servers_info_details">Detaily</string>
|
||||
<string name="servers_info_reset_stats">Resetovat všechny statistiky</string>
|
||||
<string name="please_try_later">Prosím zkuste později.</string>
|
||||
<string name="private_routing_error">Chyba soukromého směrování</string>
|
||||
<string name="network_error_broker_host_desc">Adresa serveru není kompatibilní s nastavením sítě: %1$s.</string>
|
||||
<string name="member_inactive_title">Člen neaktivní</string>
|
||||
<string name="member_inactive_desc">Zpráva může být doručena později až bude člen aktivní.</string>
|
||||
<string name="message_forwarded_desc">Zatím bez přímého spojení, zpráva je předána adminem.</string>
|
||||
<string name="cant_call_contact_connecting_wait_alert_text">Připojování ke kontaktu, počkejte nebo se podívejte později!</string>
|
||||
<string name="cant_call_contact_deleted_alert_text">Kontakt odstraněn.</string>
|
||||
<string name="deletion_errors">Chyby mazání</string>
|
||||
<string name="servers_info_detailed_statistics">Podrobné statistiky</string>
|
||||
<string name="chunks_downloaded">Části staženy</string>
|
||||
<string name="network_session_mode_server">Server</string>
|
||||
<string name="network_smp_proxy_fallback_allow_protected_description">Odesílat zprávy přímo, když je IP adresa chráněna a váš nebo cílový server nepodporuje soukromé směrování.</string>
|
||||
<string name="network_smp_proxy_fallback_allow_description">Odeslat zprávy přímo, když váš nebo cílový server nepodporuje soukromé směrování.</string>
|
||||
<string name="app_check_for_updates">Zkontrolovat aktualizace</string>
|
||||
<string name="app_check_for_updates_notice_disable">Vypnout</string>
|
||||
<string name="app_check_for_updates_disabled">Vypnut</string>
|
||||
<string name="app_check_for_updates_button_download">Stáhnout %s (%s)</string>
|
||||
<string name="invite_friends_short">Pozvat</string>
|
||||
<string name="create_address_button">Vytvořit</string>
|
||||
<string name="settings_message_shape_corner">Roh</string>
|
||||
<string name="settings_section_title_message_shape">Tvar zpráv</string>
|
||||
<string name="chat_database_exported_continue">Pokračovat</string>
|
||||
<string name="call_desktop_permission_denied_chrome">Klikněte na info tlačítko blízko pole adresy, pro použití mikrofonu.</string>
|
||||
<string name="call_desktop_permission_denied_safari">Otevřete nastavení Safari / Webové stránky / mikrofon, vyberte možnost Povolit pro localhost.</string>
|
||||
<string name="appearance_font_size">Velikost písma</string>
|
||||
<string name="v6_0_upgrade_app_descr">Stáhnout nové verze z GitHubu.</string>
|
||||
<string name="v6_0_reachable_chat_toolbar">Dosažitelný panel nástrojů chatu</string>
|
||||
<string name="migrate_from_device_remove_archive_question">Odebrat archiv?</string>
|
||||
<string name="servers_info_files_tab">Soubory</string>
|
||||
<string name="servers_info_messages_sent">Odeslaných zpráv</string>
|
||||
<string name="servers_info_downloaded">Staženo</string>
|
||||
<string name="servers_info_reconnect_server_title">Znovu připojit server?</string>
|
||||
<string name="decryption_errors">chyba dešifrování</string>
|
||||
<string name="v6_1_better_calls">Lepší volání</string>
|
||||
<string name="v6_1_better_user_experience">Větší přívětivost</string>
|
||||
<string name="v6_1_customizable_message_descr">Přizpůsobitelný tvar zpráv.</string>
|
||||
<string name="v6_1_delete_many_messages_descr">Smazat nebo moderovat až 200 zpráv.</string>
|
||||
<string name="v6_1_forward_many_messages_descr">Předat až 20 zpráv najednou.</string>
|
||||
<string name="servers_info_missing">Žádné info, zkuste načíst znovu</string>
|
||||
<string name="servers_info">Informace o serverech</string>
|
||||
<string name="downloaded_files">Stažené soubory</string>
|
||||
<string name="download_errors">Chyby stahování</string>
|
||||
<string name="servers_info_reset_stats_alert_error_title">Chyba resetování statistik</string>
|
||||
<string name="servers_info_detailed_statistics_received_messages_header">Přijaté zprávy</string>
|
||||
<string name="servers_info_detailed_statistics_received_total">Přijato celkem</string>
|
||||
<string name="servers_info_detailed_statistics_receive_errors">Chyb přijmutí</string>
|
||||
<string name="servers_info_reconnect_all_servers_button">Připojte znovu všechny servery</string>
|
||||
<string name="servers_info_reset_stats_alert_confirm">Reset</string>
|
||||
<string name="servers_info_reset_stats_alert_title">Resetovat všechny statistiky?</string>
|
||||
<string name="servers_info_detailed_statistics_sent_messages_header">Odeslané zprávy</string>
|
||||
<string name="servers_info_detailed_statistics_sent_messages_total">Odeslaných celkem</string>
|
||||
<string name="server_address">Adresa serveru</string>
|
||||
<string name="one_hand_ui">Dosažitelný panel nástrojů chatu</string>
|
||||
<string name="acknowledgement_errors">Chyba potvrzení</string>
|
||||
<string name="connections">Připojení</string>
|
||||
<string name="created">Vytvořen</string>
|
||||
<string name="expired_label">prošlý</string>
|
||||
<string name="other_label">jiné</string>
|
||||
<string name="other_errors">jiné chyby</string>
|
||||
<string name="reconnect">Znovu připojit</string>
|
||||
<string name="send_errors">Chyby odesílání</string>
|
||||
<string name="sent_directly">Odesláno přímo</string>
|
||||
<string name="sent_via_proxy">Odeslaných přes proxy</string>
|
||||
<string name="delete_members_messages__question">Odstranit %d zpráv členů?</string>
|
||||
<string name="delete_messages_mark_deleted_warning">Zprávy budou označeny pro smazání. Příjemci budou moci tyto zprávy odhalit.</string>
|
||||
<string name="select_verb">Vybrat</string>
|
||||
<string name="compose_message_placeholder">Zpráva</string>
|
||||
<string name="selected_chat_items_nothing_selected">Nic nevybráno</string>
|
||||
<string name="selected_chat_items_selected_n">Vybrány %d</string>
|
||||
<string name="privacy_media_blur_radius_medium">Střední</string>
|
||||
<string name="servers_info_subscriptions_section_header">Příjem zpráv</string>
|
||||
<string name="xftp_servers_configured">Nastavené XFTP servery</string>
|
||||
<string name="media_and_file_servers">Servery médií a souborů</string>
|
||||
<string name="message_servers">Servery zpráv</string>
|
||||
<string name="xftp_servers_other">Jiné FXTP servery</string>
|
||||
<string name="action_button_add_members">Pozvat</string>
|
||||
<string name="cant_call_member_send_message_alert_text">Pošlete zprávu pro povolení volání.</string>
|
||||
</resources>
|
||||
@@ -2119,13 +2119,13 @@
|
||||
<string name="network_session_mode_server_description">Se usarán credenciales SOCKS nuevas por cada servidor.</string>
|
||||
<string name="network_session_mode_server">Servidor</string>
|
||||
<string name="v6_1_better_calls">Llamadas mejoradas</string>
|
||||
<string name="v6_1_message_dates_descr">Mensajes mejor datados.</string>
|
||||
<string name="v6_1_message_dates_descr">Sistema de fechas mejorado.</string>
|
||||
<string name="v6_1_better_user_experience">Experiencia de usuario mejorada</string>
|
||||
<string name="v6_1_customizable_message_descr">Forma personalizable de los mensajes.</string>
|
||||
<string name="v6_1_forward_many_messages_descr">Desplazamiento de hasta 20 mensajes.</string>
|
||||
<string name="v6_1_better_security_descr">Protocolos de SimpleX auditados por Trail of Bits.</string>
|
||||
<string name="v6_1_better_calls_descr">Intercambia audio y video durante la llamada.</string>
|
||||
<string name="v6_1_better_security">Seguridad mejorada ✅</string>
|
||||
<string name="v6_1_delete_many_messages_descr">Borrar o moderar hasta 200 mensajes.</string>
|
||||
<string name="v6_1_delete_many_messages_descr">Borra o modera hasta 200 mensajes a la vez.</string>
|
||||
<string name="v6_1_switch_chat_profile_descr">Cambia el perfil de chat para invitaciones de un solo uso.</string>
|
||||
</resources>
|
||||
@@ -49,7 +49,7 @@
|
||||
<string name="socks_proxy_setting_limitations"><![CDATA[<b>Megjegyzés:</b> 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.]]></string>
|
||||
<string name="full_backup">Alkalmazásadatok biztonsági mentése</string>
|
||||
<string name="database_initialization_error_title">Az adatbázis előkészítése sikertelen</string>
|
||||
<string name="all_your_contacts_will_remain_connected_update_sent">Az ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél.</string>
|
||||
<string name="all_your_contacts_will_remain_connected_update_sent">Az ismerőseivel kapcsolatban marad. A profil-változtatások frissítésre kerülnek az ismerősöknél.</string>
|
||||
<string name="v4_5_transport_isolation_descr">A csevegési profillal (alapértelmezett), vagy a kapcsolattal (BÉTA).</string>
|
||||
<string name="connect__a_new_random_profile_will_be_shared">Egy új véletlenszerű profil lesz megosztva.</string>
|
||||
<string name="allow_voice_messages_only_if">A hangüzenetek küldése csak abban az esetben van engedélyezve, ha az ismerőse is engedélyezi.</string>
|
||||
@@ -219,7 +219,7 @@
|
||||
<string name="connecting_to_desktop">Kapcsolódás a számítógéphez</string>
|
||||
<string name="network_session_mode_entity">Kapcsolat</string>
|
||||
<string name="correct_name_to">Név helyesbítése erre: %s?</string>
|
||||
<string name="connection_timeout">Kapcsolat időtúllépés</string>
|
||||
<string name="connection_timeout">Időtúllépés kapcsolódáskor</string>
|
||||
<string name="connect_with_contact_name_question">Kapcsolódás %1$s által?</string>
|
||||
<string name="create_profile_button">Létrehozás</string>
|
||||
<string name="contact_preferences">Ismerős beállításai</string>
|
||||
@@ -357,7 +357,7 @@
|
||||
<string name="database_encryption_will_be_updated_in_settings">Adatbázis titkosítási jelmondat frissül és eltárolásra kerül a beállításokban.</string>
|
||||
<string name="info_row_database_id">Adatbázis-azonosító</string>
|
||||
<string name="share_text_database_id">Adatbázis-azonosító: %d</string>
|
||||
<string name="developer_options">Adatbázis-azonosítók és átviteli izolációs beállítások.</string>
|
||||
<string name="developer_options">Adatbázis-azonosítók és átvitel-izolációs beállítások.</string>
|
||||
<string name="database_encryption_will_be_updated">Az adatbázis-titkosítási jelmondat megváltoztatásra és mentésre kerül a Keystore-ban.</string>
|
||||
<string name="database_will_be_encrypted_and_passphrase_stored_in_settings">Az adatbázis titkosításra kerül és a jelmondat eltárolásra a beállításokban.</string>
|
||||
<string name="smp_servers_delete_server">Kiszolgáló törlése</string>
|
||||
@@ -504,7 +504,7 @@
|
||||
<string name="error_starting_chat">Hiba a csevegés elindításakor</string>
|
||||
<string name="group_profile_is_stored_on_members_devices">A csoport profilja a tagok eszközein tárolódik, nem a kiszolgálókon.</string>
|
||||
<string name="enter_passphrase">Jelmondat megadása…</string>
|
||||
<string name="error_updating_user_privacy">Hiba a felhasználói beállítások frissítésekor</string>
|
||||
<string name="error_updating_user_privacy">Hiba a felhasználói adatvédelem frissítésekor</string>
|
||||
<string name="encrypt_database">Titkosít</string>
|
||||
<string name="alert_title_no_group">Csoport nem található!</string>
|
||||
<string name="error_saving_smp_servers">Hiba az SMP-kiszolgálók mentésekor</string>
|
||||
@@ -542,7 +542,7 @@
|
||||
<string name="files_and_media">Fájlok és médiatartalmak</string>
|
||||
<string name="section_title_for_console">KONZOLHOZ</string>
|
||||
<string name="alert_text_encryption_renegotiation_failed">Sikertelen titkosítás-újraegyeztetés.</string>
|
||||
<string name="error_deleting_user">Hiba a felhasználói profil törlésekor</string>
|
||||
<string name="error_deleting_user">Hiba a felhasználó-profil törlésekor</string>
|
||||
<string name="fix_connection_not_supported_by_group_member">Csoporttag általi javítás nem támogatott</string>
|
||||
<string name="enter_welcome_message">Üdvözlőüzenet megadása…</string>
|
||||
<string name="encrypted_database">Titkosított adatbázis</string>
|
||||
@@ -642,7 +642,7 @@
|
||||
<string name="onboarding_notifications_mode_service">Azonnali</string>
|
||||
<string name="v5_4_incognito_groups">Inkognitócsoportok</string>
|
||||
<string name="how_to">Hogyan</string>
|
||||
<string name="hide_verb">Elrejtés</string>
|
||||
<string name="hide_verb">Összecsukás</string>
|
||||
<string name="gallery_image_button">Kép</string>
|
||||
<string name="v4_3_improved_privacy_and_security">Fejlesztett adatvédelem és biztonság</string>
|
||||
<string name="ignore">Mellőzés</string>
|
||||
@@ -677,7 +677,7 @@
|
||||
<string name="if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel"><![CDATA[Ha nem tud személyesen találkozni, <b>mutassa meg a QR-kódot a videohívásban</b>, vagy ossza meg a hivatkozást.]]></string>
|
||||
<string name="network_disable_socks_info">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.</string>
|
||||
<string name="image_will_be_received_when_contact_completes_uploading">A kép akkor érkezik meg, amikor a küldője befejezte annak feltöltését.</string>
|
||||
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 számítógép: a megjelenített QR-kód beolvasása az alkalmazásból, a <b>QR kód beolvasásával</b>.]]></string>
|
||||
<string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 számítógép: a megjelenített QR-kód beolvasása az alkalmazásból, a <b>QR-kód beolvasásával</b>.]]></string>
|
||||
<string name="if_you_received_simplex_invitation_link_you_can_open_in_browser">A kapott SimpleX Chat-meghívó-hivatkozását megnyithatja a böngészőjében:</string>
|
||||
<string name="if_you_enter_self_destruct_code">Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot:</string>
|
||||
<string name="found_desktop">Megtalált számítógép</string>
|
||||
@@ -686,7 +686,7 @@
|
||||
<string name="create_chat_profile">Csevegési profil létrehozása</string>
|
||||
<string name="immune_to_spam_and_abuse">Levélszemét elleni védelem</string>
|
||||
<string name="disconnect_remote_hosts">Hordozható eszközök leválasztása</string>
|
||||
<string name="v4_5_multiple_chat_profiles_descr">Különböző nevek, avatarok és átviteli izoláció.</string>
|
||||
<string name="v4_5_multiple_chat_profiles_descr">Különböző nevek, profilképek és átvitel-izoláció.</string>
|
||||
<string name="if_you_choose_to_reject_the_sender_will_not_be_notified">Elutasítás esetén a feladó NEM kap értesítést.</string>
|
||||
<string name="icon_descr_expand_role">Szerepkörválasztás bővítése</string>
|
||||
<string name="image_will_be_received_when_contact_is_online">A kép akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később!</string>
|
||||
@@ -801,10 +801,10 @@
|
||||
<string name="v4_5_multiple_chat_profiles">Több csevegőprofil</string>
|
||||
<string name="marked_deleted_description">törlésre jelölve</string>
|
||||
<string name="user_mute">Némítás</string>
|
||||
<string name="link_a_mobile">Egy hordozható eszköz társítása</string>
|
||||
<string name="link_a_mobile">Hordozható eszköz társítása</string>
|
||||
<string name="settings_notifications_mode_title">Értesítési szolgáltatás</string>
|
||||
<string name="only_group_owners_can_enable_voice">Csak a csoporttulajdonosok engedélyezhetik a hangüzenetek küldését.</string>
|
||||
<string name="only_client_devices_store_contacts_groups_e2e_encrypted_messages"><![CDATA[Csak az alkalmazások tárolják a felhasználói profilokat, ismerősöket, csoportokat és a <b>2 rétegű végpontok közötti titkosítással</b> küldött üzeneteket.]]></string>
|
||||
<string name="only_client_devices_store_contacts_groups_e2e_encrypted_messages"><![CDATA[Csak az alkalmazások tárolják a felhasználó-profilokat, ismerősöket, csoportokat és a <b>2 rétegű végpontok közötti titkosítással</b> küldött üzeneteket.]]></string>
|
||||
<string name="invalid_migration_confirmation">Érvénytelen átköltöztetési visszaigazolás</string>
|
||||
<string name="only_group_owners_can_change_prefs">Csak a csoporttulajdonosok módosíthatják a csoportbeállításokat.</string>
|
||||
<string name="no_history">Nincsenek előzmények</string>
|
||||
@@ -822,7 +822,7 @@
|
||||
<string name="feature_offered_item">ajánlott %s</string>
|
||||
<string name="button_leave_group">Csoport elhagyása</string>
|
||||
<string name="unblock_member_desc">Minden %s által írt üzenet megjelenik!</string>
|
||||
<string name="many_people_asked_how_can_it_deliver"><![CDATA[Sokan kérdezték: <i>Ha a SimpleX Chatnek nincs felhasználói azonosítója, hogyan lehet mégis üzeneteket küldeni?</i>]]></string>
|
||||
<string name="many_people_asked_how_can_it_deliver"><![CDATA[Sokan kérdezték: <i>Ha a SimpleX Chatnek nincs felhasználó-azonosítója, hogyan lehet mégis üzeneteket küldeni?</i>]]></string>
|
||||
<string name="alert_text_skipped_messages_it_can_happen_when">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.</string>
|
||||
<string name="group_member_role_observer">megfigyelő</string>
|
||||
<string name="description_via_group_link_incognito">inkognitó a csoporthivatkozáson keresztül</string>
|
||||
@@ -893,13 +893,13 @@
|
||||
<string name="add_contact_or_create_group">Új csevegés kezdése</string>
|
||||
<string name="opensource_protocol_and_code_anybody_can_run_servers">Bárki üzemeltethet kiszolgálókat.</string>
|
||||
<string name="rcv_group_event_open_chat">Megnyitás</string>
|
||||
<string name="network_option_protocol_timeout">Protokoll időtúllépés</string>
|
||||
<string name="network_option_protocol_timeout">Protokoll időtúllépése</string>
|
||||
<string name="secret_text">titkos</string>
|
||||
<string name="settings_notification_preview_mode_title">Értesítés előnézete</string>
|
||||
<string name="callstate_waiting_for_confirmation">várakozás a visszaigazolásra…</string>
|
||||
<string name="stop_file__action">Fájl megállítása</string>
|
||||
<string name="description_via_group_link">a csoporthivatkozáson keresztül</string>
|
||||
<string name="network_option_ping_interval">PING időköze</string>
|
||||
<string name="network_option_ping_interval">Időtartam a PING-ek között</string>
|
||||
<string name="send_disappearing_message">Eltűnő üzenet küldése</string>
|
||||
<string name="self_destruct_passcode">Önmegsemmisítési jelkód</string>
|
||||
<string name="save_and_update_group_profile">Mentés és a csoportprofil frissítése</string>
|
||||
@@ -979,12 +979,12 @@
|
||||
<string name="reject_contact_button">Elutasítás</string>
|
||||
<string name="notification_preview_mode_message_desc">Ismerős nevének és az üzenet tartalmának megjelenítése</string>
|
||||
<string name="settings_section_title_settings">BEÁLLÍTÁSOK</string>
|
||||
<string name="save_profile_password">Felhasználói fiók jelszavának mentése</string>
|
||||
<string name="save_profile_password">Profiljelszó mentése</string>
|
||||
<string name="stop_snd_file__title">Fájlküldés megállítása?</string>
|
||||
<string name="unlink_desktop_question">Számítógép leválasztása?</string>
|
||||
<string name="voice_messages_prohibited">A hangüzenetek le vannak tilva!</string>
|
||||
<string name="compose_send_direct_message_to_connect">Közvetlen üzenet küldése a kapcsolódáshoz</string>
|
||||
<string name="network_option_ping_count">PING számláló</string>
|
||||
<string name="network_option_ping_count">PING-ek száma</string>
|
||||
<string name="show_developer_options">Fejlesztői beállítások megjelenítése</string>
|
||||
<string name="rcv_group_event_1_member_connected">%s kapcsolódott</string>
|
||||
<string name="theme_system">Rendszer</string>
|
||||
@@ -1065,7 +1065,7 @@
|
||||
<string name="onboarding_notifications_mode_title">Privát értesítések</string>
|
||||
<string name="you_invited_a_contact">Meghívta egy ismerősét</string>
|
||||
<string name="is_not_verified">%s nincs hitelesítve</string>
|
||||
<string name="contact_tap_to_connect">Koppintson a kapcsolódáshoz</string>
|
||||
<string name="contact_tap_to_connect">Koppintson ide a kapcsolódáshoz</string>
|
||||
<string name="this_device_name">Ennek az eszköznek a neve</string>
|
||||
<string name="your_current_profile">Jelenlegi profil</string>
|
||||
<string name="smp_server_test_upload_file">Fájl feltöltése</string>
|
||||
@@ -1124,7 +1124,7 @@
|
||||
<string name="settings_section_title_use_from_desktop">Társítás számítógéppel</string>
|
||||
<string name="settings_section_title_you">PROFIL</string>
|
||||
<string name="network_proxy_port">port %d</string>
|
||||
<string name="to_connect_via_link_title">Kapcsolódás hivatkozáson keresztül</string>
|
||||
<string name="to_connect_via_link_title">Kapcsolódás egy hivatkozáson keresztül</string>
|
||||
<string name="share_address">Cím megosztása</string>
|
||||
<string name="smp_servers_scan_qr">A kiszolgáló QR-kódjának beolvasása</string>
|
||||
<string name="stop_chat_confirmation">Megállítás</string>
|
||||
@@ -1218,7 +1218,7 @@
|
||||
<string name="snd_group_event_member_deleted">Ön eltávolította őt: %1$s</string>
|
||||
<string name="save_passphrase_and_open_chat">Jelmondat mentése és a csevegés megnyitása</string>
|
||||
<string name="save_preferences_question">Beállítások mentése?</string>
|
||||
<string name="first_platform_without_user_ids">Nincsenek felhasználói azonosítók.</string>
|
||||
<string name="first_platform_without_user_ids">Nincsenek felhasználó-azonosítók.</string>
|
||||
<string name="prohibit_direct_messages">A közvetlen üzenetek küldése a tagok között le van tiltva.</string>
|
||||
<string name="network_enable_socks">SOCKS proxy használata?</string>
|
||||
<string name="icon_descr_speaker_off">Hangszóró kikapcsolva</string>
|
||||
@@ -1279,14 +1279,14 @@
|
||||
<string name="use_simplex_chat_servers__question">SimpleX Chat-kiszolgálók használata?</string>
|
||||
<string name="unhide_chat_profile">Csevegési profil felfedése</string>
|
||||
<string name="v5_0_large_files_support">Videók és fájlok 1Gb méretig</string>
|
||||
<string name="network_option_tcp_connection_timeout">TCP kapcsolat időtúllépés</string>
|
||||
<string name="network_option_tcp_connection_timeout">TCP kapcsolat időtúllépése</string>
|
||||
<string name="connect__your_profile_will_be_shared">A(z) %1$s nevű profiljának SimpleX-címe megosztásra fog kerülni.</string>
|
||||
<string name="you_are_already_connected_to_vName_via_this_link">Ön már kapcsolódva van ehhez: %1$s.</string>
|
||||
<string name="your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one">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.</string>
|
||||
<string name="chat_with_the_founder">Ötletek és javaslatok</string>
|
||||
<string name="database_downgrade_warning">Figyelmeztetés: néhány adat elveszhet!</string>
|
||||
<string name="tap_to_start_new_chat">Koppintson az új csevegés indításához</string>
|
||||
<string name="tap_to_start_new_chat">Koppintson ide az új csevegés indításához</string>
|
||||
<string name="waiting_for_desktop">Várakozás a számítógépre…</string>
|
||||
<string name="next_generation_of_private_messaging">A privát üzenetküldés
|
||||
\nkövetkező generációja</string>
|
||||
@@ -1311,7 +1311,7 @@
|
||||
<string name="restore_passphrase_not_found_desc">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.</string>
|
||||
<string name="your_contacts_will_remain_connected">Az ismerősei továbbra is kapcsolódva maradnak.</string>
|
||||
<string name="error_xftp_test_server_auth">A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát</string>
|
||||
<string name="database_initialization_error_desc">Az adatbázis nem működik megfelelően. Koppintson további információért</string>
|
||||
<string name="database_initialization_error_desc">Az adatbázis nem működik megfelelően. Koppintson ide a további információkért</string>
|
||||
<string name="stop_snd_file__message">A fájl küldése leállt.</string>
|
||||
<string name="trying_to_connect_to_server_to_receive_messages">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.</string>
|
||||
<string name="la_could_not_be_verified">Nem sikerült hitelesíteni; próbálja meg újra.</string>
|
||||
@@ -1368,7 +1368,7 @@
|
||||
<string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[A csatlakozás már folyamatban van a(z) <b>%1$s</b> nevű csoporthoz.]]></string>
|
||||
<string name="onboarding_notifications_mode_off">Amikor az alkalmazás fut</string>
|
||||
<string name="alert_title_cant_invite_contacts_descr">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</string>
|
||||
<string name="v4_5_transport_isolation">Kapcsolat izolációs mód</string>
|
||||
<string name="v4_5_transport_isolation">Átvitel-izoláció</string>
|
||||
<string name="you_will_be_connected_when_your_connection_request_is_accepted">Akkor lesz kapcsolódva, ha a kapcsolatkérése elfogadásra kerül, várjon, vagy ellenőrizze később!</string>
|
||||
<string name="voice_messages_are_prohibited">A hangüzenetek küldése le van tiltva ebben a csoportban.</string>
|
||||
<string name="system_restricted_background_in_call_warn"><![CDATA[A háttérben való hívásokhoz válassza ki az <b>Alkalmazás akkumulátor-használata</b> / <b>Korlátlan</b> módot az alkalmazás beállításaiban.]]></string>
|
||||
@@ -1386,11 +1386,11 @@
|
||||
<string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">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.</string>
|
||||
<string name="snd_group_event_changed_member_role">Ön megváltoztatta %s szerepkörét erre: %s</string>
|
||||
<string name="you_rejected_group_invitation">Csoportmeghívó elutasítva</string>
|
||||
<string name="to_protect_privacy_simplex_has_ids_for_queues">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.</string>
|
||||
<string name="to_protect_privacy_simplex_has_ids_for_queues">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.</string>
|
||||
<string name="to_share_with_your_contact">(a megosztáshoz az ismerősével)</string>
|
||||
<string name="you_sent_group_invitation">Csoportmeghívó elküldve</string>
|
||||
<string name="update_network_session_mode_question">Kapcsolat izolációs mód frissítése?</string>
|
||||
<string name="network_session_mode_transport_isolation">Kapcsolat izolációs mód</string>
|
||||
<string name="update_network_session_mode_question">Átvitel-izoláció módjának frissítése?</string>
|
||||
<string name="network_session_mode_transport_isolation">Átvitel-izoláció</string>
|
||||
<string name="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.</string>
|
||||
<string name="database_is_not_encrypted">A csevegési adatbázis nem titkosított - állítson be egy jelmondatot annak védelméhez.</string>
|
||||
<string name="network_disable_socks">Közvetlen internet kapcsolat használata?</string>
|
||||
@@ -1421,7 +1421,7 @@
|
||||
<string name="you_can_connect_to_simplex_chat_founder"><![CDATA[Kapcsolatba léphet <font color="#0088ff">a SimpleX Chat fejlesztőivel, ahol bármiről kérdezhet és értesülhet az újdonságokról</font>.]]></string>
|
||||
<string name="v4_2_auto_accept_contact_requests_desc">Nem kötelező üdvözlőüzenettel.</string>
|
||||
<string name="unknown_database_error_with_info">Ismeretlen adatbázishiba: %s</string>
|
||||
<string name="you_can_hide_or_mute_user_profile">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.</string>
|
||||
<string name="you_can_hide_or_mute_user_profile">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.</string>
|
||||
<string name="v5_3_simpler_incognito_mode_descr">Inkognitómód használata kapcsolódáskor.</string>
|
||||
<string name="you_can_share_group_link_anybody_will_be_able_to_connect">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.</string>
|
||||
<string name="you_joined_this_group">Csatlakozott ehhez a csoporthoz</string>
|
||||
@@ -1438,12 +1438,12 @@
|
||||
<string name="v4_6_group_welcome_message_descr">Megjelenítendő üzenet beállítása az új tagok számára!</string>
|
||||
<string name="whats_new_thanks_to_users_contribute_weblate">Köszönet a felhasználóknak - hozzájárulás a Weblate-en!</string>
|
||||
<string name="sending_delivery_receipts_will_be_enabled">A kézbesítési jelentés küldése minden ismerőse számára engedélyezésre kerül.</string>
|
||||
<string name="network_option_protocol_timeout_per_kb">Protokoll időkorlát KB-onként</string>
|
||||
<string name="network_option_protocol_timeout_per_kb">Protokoll időtúllépése KB-onként</string>
|
||||
<string name="database_backup_can_be_restored">Az adatbázis-jelmondat megváltoztatására tett kísérlet nem fejeződött be.</string>
|
||||
<string name="enable_automatic_deletion_message">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.</string>
|
||||
<string name="profile_is_only_shared_with_your_contacts">A profilja csak az ismerőseivel kerül megosztásra.</string>
|
||||
<string name="smp_servers_test_some_failed">Néhány kiszolgáló megbukott a teszten:</string>
|
||||
<string name="group_invitation_tap_to_join">Koppintson a csatlakozáshoz</string>
|
||||
<string name="group_invitation_tap_to_join">Koppintson ide a csatlakozáshoz</string>
|
||||
<string name="delete_files_and_media_desc">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.</string>
|
||||
<string name="receipts_contacts_override_enabled">A kézbesítési jelentések engedélyezve vannak %d ismerősnél</string>
|
||||
<string name="sending_via">Küldés ezen keresztül:</string>
|
||||
@@ -1457,7 +1457,7 @@
|
||||
<string name="contact_you_shared_link_with_wont_be_able_to_connect">Az ismerőse, akivel megosztotta ezt a hivatkozást, NEM fog tudni kapcsolódni!</string>
|
||||
<string name="you_can_change_it_later">A véletlenszerű jelmondat egyszerű szövegként van tárolva a beállításokban.
|
||||
\nEz később megváltoztatható.</string>
|
||||
<string name="group_invitation_tap_to_join_incognito">Koppintson az inkognitóban való kapcsolódáshoz</string>
|
||||
<string name="group_invitation_tap_to_join_incognito">Koppintson ide az inkognitóban való kapcsolódáshoz</string>
|
||||
<string name="set_password_to_export">Jelmondat beállítása az exportáláshoz</string>
|
||||
<string name="receipts_groups_override_disabled">A kézbesítési jelentések le vannak tiltva %d csoportban</string>
|
||||
<string name="non_fatal_errors_occured_during_import">Néhány nem végzetes hiba történt az importáláskor:</string>
|
||||
@@ -1519,7 +1519,7 @@
|
||||
<string name="recent_history">Látható előzmények</string>
|
||||
<string name="la_app_passcode">Alkalmazás jelkód</string>
|
||||
<string name="add_contact_tab">Ismerős hozzáadása</string>
|
||||
<string name="tap_to_scan">Koppintson a beolvasáshoz</string>
|
||||
<string name="tap_to_scan">Koppintson ide a QR-kód beolvasásához</string>
|
||||
<string name="tap_to_paste_link">Koppintson ide a hivatkozás beillesztéséhez</string>
|
||||
<string name="add_contact_button_to_create_link_or_connect_via_link"><![CDATA[<b>Ismerős hozzáadása</b>: új meghívó-hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő kapcsolódáshoz.]]></string>
|
||||
<string name="create_group_button_to_create_new_group"><![CDATA[<b>Csoport létrehozása</b>: új csoport létrehozásához.]]></string>
|
||||
@@ -1768,7 +1768,7 @@
|
||||
<string name="network_smp_proxy_mode_never">Soha</string>
|
||||
<string name="network_smp_proxy_mode_unknown">Ismeretlen kiszolgálók</string>
|
||||
<string name="network_smp_proxy_fallback_allow_protected">Ha az IP-cím rejtett</string>
|
||||
<string name="private_routing_show_message_status">Üzenet állapot megjelenítése</string>
|
||||
<string name="private_routing_show_message_status">Üzenetállapot megjelenítése</string>
|
||||
<string name="network_smp_proxy_fallback_allow_downgrade">Visszafejlesztés engedélyezése</string>
|
||||
<string name="network_smp_proxy_mode_always">Mindig</string>
|
||||
<string name="network_smp_proxy_fallback_prohibit">Nem</string>
|
||||
@@ -1874,8 +1874,7 @@
|
||||
<string name="servers_info_sessions_connecting">Kapcsolódás</string>
|
||||
<string name="servers_info_sessions_errors">Hibák</string>
|
||||
<string name="servers_info_subscriptions_connections_pending">Függőben</string>
|
||||
<string name="servers_info_private_data_disclaimer">Ekkortól kezdve: %s.
|
||||
\nMinden adat biztonságban van az eszközén.</string>
|
||||
<string name="servers_info_private_data_disclaimer">Statisztikagyűjtés kezdete: %s.\nMinden adat biztonságban van az eszközén.</string>
|
||||
<string name="servers_info_messages_sent">Elküldött üzenetek</string>
|
||||
<string name="servers_info_proxied_servers_section_header">Proxyzott kiszolgálók</string>
|
||||
<string name="servers_info_reconnect_servers_title">Újrakapcsolódás a kiszolgálókhoz?</string>
|
||||
@@ -1891,15 +1890,15 @@
|
||||
<string name="servers_info_downloaded">Letöltve</string>
|
||||
<string name="expired_label">lejárt</string>
|
||||
<string name="other_label">egyéb</string>
|
||||
<string name="servers_info_detailed_statistics_received_total">Összes fogadott</string>
|
||||
<string name="servers_info_detailed_statistics_received_total">Összes fogadott üzenet</string>
|
||||
<string name="servers_info_detailed_statistics_receive_errors">Üzenetfogadási hibák</string>
|
||||
<string name="reconnect">Újrakapcsolás</string>
|
||||
<string name="send_errors">Üzenetküldési hibák</string>
|
||||
<string name="sent_directly">Közvetlenül küldött</string>
|
||||
<string name="servers_info_detailed_statistics_sent_messages_total">Összes elküldött</string>
|
||||
<string name="servers_info_detailed_statistics_sent_messages_total">Összes elküldött üzenet</string>
|
||||
<string name="sent_via_proxy">Proxyn keresztül küldve</string>
|
||||
<string name="smp_server">SMP-kiszolgáló</string>
|
||||
<string name="servers_info_starting_from">Ekkortól kezdve: %s.</string>
|
||||
<string name="servers_info_starting_from">Statisztikagyűjtés kezdete: %s.</string>
|
||||
<string name="servers_info_uploaded">Feltöltve</string>
|
||||
<string name="xftp_server">XFTP-kiszolgáló</string>
|
||||
<string name="proxied">Proxyzott</string>
|
||||
@@ -1927,7 +1926,7 @@
|
||||
<string name="xftp_servers_configured">Konfigurált XFTP-kiszolgálók</string>
|
||||
<string name="servers_info_sessions_connected">Kapcsolódva</string>
|
||||
<string name="current_user">Jelenlegi profil</string>
|
||||
<string name="servers_info_details">Részletek</string>
|
||||
<string name="servers_info_details">További részletek</string>
|
||||
<string name="decryption_errors">visszafejtési hibák</string>
|
||||
<string name="deleted">Törölve</string>
|
||||
<string name="servers_info_messages_received">Fogadott üzenetek</string>
|
||||
@@ -1944,7 +1943,7 @@
|
||||
<string name="servers_info_reconnect_server_message">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.</string>
|
||||
<string name="servers_info_detailed_statistics_sent_messages_header">Elküldött üzenetek</string>
|
||||
<string name="servers_info_transport_sessions_section_header">Munkamenetek átvitele</string>
|
||||
<string name="servers_info_subscriptions_total">Összesen</string>
|
||||
<string name="servers_info_subscriptions_total">Összes kapcsolat</string>
|
||||
<string name="servers_info_statistics_section_header">Statisztikák</string>
|
||||
<string name="servers_info_target">Információk megjelenítése ehhez:</string>
|
||||
<string name="network_error_broker_version_desc">A kiszolgáló verziója nem kompatibilis az alkalmazással: %1$s.</string>
|
||||
|
||||
@@ -2120,7 +2120,7 @@
|
||||
<string name="v6_1_message_dates_descr">Betere datums voor berichten.</string>
|
||||
<string name="v6_1_better_user_experience">Betere gebruikerservaring</string>
|
||||
<string name="v6_1_customizable_message_descr">Aanpasbare berichtvorm.</string>
|
||||
<string name="v6_1_better_calls_descr">Wissel tussen audio en video tijdens het gesprek.</string>
|
||||
<string name="v6_1_better_calls_descr">Wisselen tussen audio en video tijdens het gesprek.</string>
|
||||
<string name="v6_1_switch_chat_profile_descr">Wijzig chatprofiel voor eenmalige uitnodigingen.</string>
|
||||
<string name="v6_1_delete_many_messages_descr">Maximaal 200 berichten verwijderen of modereren.</string>
|
||||
<string name="v6_1_forward_many_messages_descr">Stuur maximaal 20 berichten tegelijk door.</string>
|
||||
|
||||
@@ -2198,7 +2198,7 @@
|
||||
<string name="v6_1_forward_many_messages_descr">Пересылайте до 20 сообщений за раз.</string>
|
||||
<string name="v6_1_better_calls_descr">Переключайте звук и видео во время звонка.</string>
|
||||
<string name="v6_1_switch_chat_profile_descr">Переключайте профиль чата для одноразовых приглашений.</string>
|
||||
<string name="v6_1_better_security_descr">Анализ безопасности протоколов SimpleX от Trail of Bits.</string>
|
||||
<string name="v6_1_better_security_descr">Аудит SimpleX протоколов от Trail of Bits.</string>
|
||||
<string name="call_desktop_permission_denied_title">Чтобы совершать звонки, разрешите использовать микрофон. Завершите вызов и попробуйте позвонить снова.</string>
|
||||
<string name="network_proxy_auth_mode_no_auth">Не использовать учетные данные с прокси.</string>
|
||||
<string name="network_proxy_incorrect_config_title">Ошибка сохранения прокси</string>
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
@@ -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.1
|
||||
android.version_code=249
|
||||
|
||||
desktop.version_name=6.1-beta.4
|
||||
desktop.version_code=71
|
||||
desktop.version_name=6.1.1
|
||||
desktop.version_code=74
|
||||
|
||||
kotlin.version=1.9.23
|
||||
gradle.plugin.version=8.2.0
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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."
|
||||
|
||||
@@ -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.")
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -0,0 +1,192 @@
|
||||
---
|
||||
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/20221108-trail-of-bits.jpg
|
||||
previewBody: blog_previews/20241014.html
|
||||
permalink: "/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html"
|
||||
|
||||
---
|
||||
|
||||
# SimpleX network: security review of protocols design by Trail of Bits, v6.1 released with better calls and user experience.
|
||||
|
||||
**Published:** Oct 14, 2024
|
||||
|
||||
[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.
|
||||
|
||||
[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
|
||||
|
||||
<img src="./images/20221108-trail-of-bits.jpg" width=240 class="float-to-right">
|
||||
|
||||
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
|
||||
|
||||
<img src="./images/20241014-calls.png" width="288" class="float-to-right">
|
||||
|
||||
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
|
||||
|
||||
<img src="./images/20241014-messages.png" width="288" class="float-to-right">
|
||||
|
||||
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
|
||||
|
||||
<img src="./images/20241014-profiles2.png" width="288" class="float-to-right"> <img src="./images/20241014-profiles1.png" width="288" class="float-to-right">
|
||||
|
||||
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
|
||||
|
||||
<img src="./images/20241014-forward.png" width="288" class="float-to-right">
|
||||
|
||||
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
|
||||
@@ -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
|
||||
|
||||
<img src="./images/20241016-wired-privacy.jpg" width="330" class="float-to-right">
|
||||
|
||||
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.
|
||||
+12
-1
@@ -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.
|
||||
|
||||
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 286 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 582 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 241 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 364 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 360 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 42 KiB |
+1
-1
@@ -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: 0b4492879fd36f0b517dca8f5e36dba187bba686
|
||||
|
||||
source-repository-package
|
||||
type: git
|
||||
|
||||
+4
-2
@@ -8,9 +8,11 @@ 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).
|
||||
|
||||
We are planning design review of SimpleX protocols in July 2024 and implementation review in December 2024/January 2025.
|
||||
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 implementation security assessment in early 2025.
|
||||
|
||||
## Reporting security issues
|
||||
|
||||
|
||||
+414
-62
@@ -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 <command> -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 `<hostnames>` 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: <YOUR_SERVER_LOCATION>
|
||||
operator: <YOUR_NAME>
|
||||
operator_country: <YOUR_LOCATION>
|
||||
website: <WEBSITE_IF_AVAILABLE>
|
||||
# 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: <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_email:
|
||||
# complaints_pgp:
|
||||
# complaints_pgp_fingerprint:
|
||||
|
||||
# Hosting provider.
|
||||
hosting: <HOSTING_PROVIDER_NAME>
|
||||
hosting_country: <HOSTING_PROVIDER_LOCATION>
|
||||
# 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: <your server domain/ip>
|
||||
port: 5223
|
||||
# Host is only used to print server address on start.
|
||||
# You can specify multiple server ports.
|
||||
host: <domain/ip>
|
||||
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: <your domain suffixes>
|
||||
# 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/ <server_user>@<server_address>:/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: <domain/ip>
|
||||
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 `<YOUR_DOMAIN>`):
|
||||
3. Replace Caddy configuration with the following:
|
||||
|
||||
Please replace `YOUR_DOMAIN` with your actual domain (smp.example.com).
|
||||
|
||||
```sh
|
||||
vim /etc/caddy/Caddyfile
|
||||
```
|
||||
|
||||
```caddy
|
||||
<YOUR_DOMAIN> {
|
||||
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://<fingerprint>[:<password>]@<public_hostname>[,<onion_hostname>]
|
||||
|
||||
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=<version_from_github_releases> &&\
|
||||
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.
|
||||
|
||||
|
||||
Binary file not shown.
@@ -6,7 +6,7 @@ revision: 16.07.2024
|
||||
|
||||
# Transparency Reports
|
||||
|
||||
**Updated**: Jul 16, 2024
|
||||
**Updated**: Oct 14, 2024
|
||||
|
||||
SimpleX Chat Ltd. is a company registered in the UK – it develops communication software enabling users to operate and communicate via SimpleX network, without user profile identifiers of any kind, and without having their data hosted by any network infrastructure operators.
|
||||
|
||||
@@ -23,6 +23,9 @@ Our objective is to consistently ensure that no user data and absolute minimum o
|
||||
- [Trust in servers](https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md#trust-in-servers)
|
||||
- [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).
|
||||
|
||||
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).
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
|
||||
+1
-1
@@ -1,5 +1,5 @@
|
||||
name: simplex-chat
|
||||
version: 6.1.0.7
|
||||
version: 6.1.1.0
|
||||
#synopsis:
|
||||
#description:
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
|
||||
@@ -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)
|
||||
}
|
||||
|
||||
@@ -11,4 +11,8 @@ package direct-sqlcipher
|
||||
extra-lib-dirs: /opt/homebrew/opt/openssl@1.1/lib
|
||||
flags: +openssl
|
||||
|
||||
package rocksdb-haskell-jprupp
|
||||
extra-include-dirs: /opt/homebrew/opt/rocksdb/include
|
||||
extra-lib-dirs: /opt/homebrew/opt/rocksdb/lib
|
||||
|
||||
test-show-details: direct
|
||||
|
||||
@@ -38,6 +38,30 @@
|
||||
</description>
|
||||
|
||||
<releases>
|
||||
<release version="6.1.0" date="2024-10-14">
|
||||
<url type="details">https://simplex.chat/blog/20241014-simplex-network-v6-1-security-review-better-calls-user-experience.html</url>
|
||||
<description>
|
||||
<p>New in v6.1:</p>
|
||||
<p>Better security:</p>
|
||||
<ul>
|
||||
<li>SimpleX protocols reviewed by Trail of Bits.</li>
|
||||
<li>security improvements (don't worry, there is nothing critical there).</li>
|
||||
</ul>
|
||||
<p>Better calls:</p>
|
||||
<ul>
|
||||
<li>you can switch audio and video during the call.</li>
|
||||
<li>share the screen from desktop app.</li>
|
||||
</ul>
|
||||
<p>Better user experience:</p>
|
||||
<ul>
|
||||
<li>switch chat profile for 1-time invitations.</li>
|
||||
<li>customizable message shape.</li>
|
||||
<li>better message dates.</li>
|
||||
<li>forward up to 20 messages at once.</li>
|
||||
<li>delete or moderate up to 200 messages.</li>
|
||||
</ul>
|
||||
</description>
|
||||
</release>
|
||||
<release version="6.0.4" date="2024-08-31">
|
||||
<url type="details">https://simplex.chat/blog/20240814-simplex-chat-vision-funding-v6-private-routing-new-user-experience.html</url>
|
||||
<description>
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
{
|
||||
"https://github.com/simplex-chat/simplexmq.git"."b8971a31bcb82fffabcb792c9afd6bc4a96ec649" = "1p6m390ngcsp7i7vy0m0zxh167gkbciavva9a00l6pxwzaz9qmpi";
|
||||
"https://github.com/simplex-chat/simplexmq.git"."0b4492879fd36f0b517dca8f5e36dba187bba686" = "0rrkq84vx9n6rq67v0awfgnwpq3mmyx3rdzg6k9xwir1397bhf7r";
|
||||
"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";
|
||||
|
||||
+2
-1
@@ -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.1.0
|
||||
category: Web, System, Services, Cryptography
|
||||
homepage: https://github.com/simplex-chat/simplex-chat#readme
|
||||
author: simplex.chat
|
||||
@@ -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
|
||||
|
||||
+99
-53
@@ -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
|
||||
@@ -1299,12 +1300,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 ->
|
||||
@@ -1450,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)
|
||||
@@ -1763,7 +1775,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 +1786,28 @@ 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 +2059,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 +2646,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 +2888,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 +3697,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 +4596,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
|
||||
@@ -8021,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),
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -1416,6 +1423,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 #-}
|
||||
@@ -1531,6 +1542,8 @@ $(JQ.deriveJSON defaultJSON ''UserProfileUpdateSummary)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''NtfMsgInfo)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''NtfConn)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''NtfMsgAckInfo)
|
||||
|
||||
$(JQ.deriveJSON defaultJSON ''SwitchProgress)
|
||||
|
||||
@@ -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;
|
||||
|]
|
||||
@@ -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);
|
||||
|
||||
@@ -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_)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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 =
|
||||
|
||||
@@ -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"
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user