diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 5be607567f..f28648d036 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -270,7 +270,7 @@ jobs: - name: Unix test if: matrix.os != 'windows-latest' - timeout-minutes: 30 + timeout-minutes: 40 shell: bash run: cabal test --test-show-details=direct diff --git a/Dockerfile b/Dockerfile index 834f2374a6..6c60195f97 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,32 +1,41 @@ -FROM ubuntu:focal AS build +ARG TAG=22.04 -# Install curl and simplex-chat-related dependencies -RUN apt-get update && apt-get install -y curl git build-essential libgmp3-dev zlib1g-dev libssl-dev +FROM ubuntu:${TAG} AS build + +### Build stage + +# Install curl and git and simplex-chat dependencies +RUN apt-get update && apt-get install -y curl git build-essential libgmp3-dev zlib1g-dev llvm-12 llvm-12-dev libnuma-dev libssl-dev + +# Specify bootstrap Haskell versions +ENV BOOTSTRAP_HASKELL_GHC_VERSION=9.6.3 +ENV BOOTSTRAP_HASKELL_CABAL_VERSION=3.10.1.0 # Install ghcup -RUN a=$(arch); curl https://downloads.haskell.org/~ghcup/$a-linux-ghcup -o /usr/bin/ghcup && \ - chmod +x /usr/bin/ghcup - -# Install ghc -RUN ghcup install ghc 9.6.3 -# Install cabal -RUN ghcup install cabal 3.10.1.0 -# Set both as default -RUN ghcup set ghc 9.6.3 && \ - ghcup set cabal 3.10.1.0 - -COPY . /project -WORKDIR /project +RUN curl --proto '=https' --tlsv1.2 -sSf https://get-ghcup.haskell.org | BOOTSTRAP_HASKELL_NONINTERACTIVE=1 sh # Adjust PATH ENV PATH="/root/.cabal/bin:/root/.ghcup/bin:$PATH" +# Set both as default +RUN ghcup set ghc "${BOOTSTRAP_HASKELL_GHC_VERSION}" && \ + ghcup set cabal "${BOOTSTRAP_HASKELL_CABAL_VERSION}" + +COPY . /project +WORKDIR /project + # Adjust build RUN cp ./scripts/cabal.project.local.linux ./cabal.project.local # Compile simplex-chat RUN cabal update -RUN cabal install +RUN cabal build exe:simplex-chat +# Strip the binary from debug symbols to reduce size +RUN bin=$(find /project/dist-newstyle -name "simplex-chat" -type f -executable) && \ + mv "$bin" ./ && \ + strip ./simplex-chat + +# Copy compiled app from build stage FROM scratch AS export-stage -COPY --from=build /root/.cabal/bin/simplex-chat / +COPY --from=build /project/simplex-chat / diff --git a/README.md b/README.md index f6630ebbfd..4cd7b2b787 100644 --- a/README.md +++ b/README.md @@ -234,6 +234,8 @@ You can use SimpleX with your own servers and still communicate with people usin Recent and important updates: +[Mar 14, 2024. SimpleX Chat v5.6 beta: adding quantum resistance to Signal double ratchet algorithm.](./blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md) + [Jan 24, 2024. SimpleX Chat: free infrastructure from Linode, v5.5 released with private notes, group history and a simpler UX to connect.](./blog/20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.md) [Nov 25, 2023. SimpleX Chat v5.4 released: link mobile and desktop apps via quantum resistant protocol, and much better groups](./blog/20231125-simplex-chat-v5-4-link-mobile-desktop-quantum-resistant-better-groups.md). diff --git a/apps/ios/Shared/ContentView.swift b/apps/ios/Shared/ContentView.swift index 45e0332dab..acea38e69e 100644 --- a/apps/ios/Shared/ContentView.swift +++ b/apps/ios/Shared/ContentView.swift @@ -34,6 +34,8 @@ struct ContentView: View { @State private var waitingForOrPassedAuth = true @State private var chatListActionSheet: ChatListActionSheet? = nil + private let callTopPadding: CGFloat = 50 + private enum ChatListActionSheet: Identifiable { case planAndConnectSheet(sheet: PlanAndConnectActionSheet) @@ -50,16 +52,28 @@ struct ContentView: View { var body: some View { ZStack { + let showCallArea = chatModel.activeCall != nil && chatModel.activeCall?.callState != .waitCapabilities && chatModel.activeCall?.callState != .invitationAccepted // contentView() has to be in a single branch, so that enabling authentication doesn't trigger re-rendering and close settings. // i.e. with separate branches like this settings are closed: `if prefPerformLA { ... contentView() ... } else { contentView() } if !prefPerformLA || accessAuthenticated { contentView() + .padding(.top, showCallArea ? callTopPadding : 0) } else { lockButton() + .padding(.top, showCallArea ? callTopPadding : 0) } + + if showCallArea, let call = chatModel.activeCall { + VStack { + activeCallInteractiveArea(call) + Spacer() + } + } + if chatModel.showCallView, let call = chatModel.activeCall { callView(call) } + if !showSettings, let la = chatModel.laRequest { LocalAuthView(authRequest: la) .onDisappear { @@ -135,11 +149,11 @@ struct ContentView: View { if case .onboardingComplete = step, chatModel.currentUser != nil { mainView() - .actionSheet(item: $chatListActionSheet) { sheet in - switch sheet { - case let .planAndConnectSheet(sheet): return planAndConnectActionSheet(sheet, dismiss: false) + .actionSheet(item: $chatListActionSheet) { sheet in + switch sheet { + case let .planAndConnectSheet(sheet): return planAndConnectActionSheet(sheet, dismiss: false) + } } - } } else { OnboardingView(onboarding: step) } @@ -163,6 +177,40 @@ struct ContentView: View { } } + @ViewBuilder private func activeCallInteractiveArea(_ call: Call) -> some View { + HStack { + Text(call.contact.displayName).font(.body).foregroundColor(.white) + Spacer() + CallDuration(call: call) + } + .padding(.horizontal) + .frame(height: callTopPadding - 10) + .background(Color(uiColor: UIColor(red: 47/255, green: 208/255, blue: 88/255, alpha: 1))) + .onTapGesture { + chatModel.activeCallViewIsCollapsed = false + } + } + + struct CallDuration: View { + let call: Call + @State var text: String = "" + @State var timer: Timer? = nil + + var body: some View { + Text(text).frame(minWidth: text.count <= 5 ? 52 : 77, alignment: .leading).offset(x: 4).font(.body).foregroundColor(.white) + .onAppear { + timer = Timer.scheduledTimer(withTimeInterval: 0.3, repeats: true) { timer in + if let connectedAt = call.connectedAt { + text = durationText(Int(Date.now.timeIntervalSince1970 - connectedAt.timeIntervalSince1970)) + } + } + } + .onDisappear { + _ = timer?.invalidate() + } + } + } + private func lockButton() -> some View { Button(action: authenticateContentViewAccess) { Label("Unlock", systemImage: "lock") } } diff --git a/apps/ios/Shared/Model/ChatModel.swift b/apps/ios/Shared/Model/ChatModel.swift index c31ad579ab..462699e407 100644 --- a/apps/ios/Shared/Model/ChatModel.swift +++ b/apps/ios/Shared/Model/ChatModel.swift @@ -80,6 +80,7 @@ final class ChatModel: ObservableObject { @Published var tokenRegistered = false @Published var tokenStatus: NtfTknStatus? @Published var notificationMode = NotificationsMode.off + @Published var notificationServer: String? @Published var notificationPreview: NotificationPreviewMode = ntfPreviewModeGroupDefault.get() // pending notification actions @Published var ntfContactRequest: NTFContactRequest? @@ -89,10 +90,12 @@ final class ChatModel: ObservableObject { @Published var activeCall: Call? let callCommand: WebRTCCommandProcessor = WebRTCCommandProcessor() @Published var showCallView = false + @Published var activeCallViewIsCollapsed = false // remote desktop @Published var remoteCtrlSession: RemoteCtrlSession? // currently showing invitation @Published var showingInvitation: ShowingInvitation? + @Published var migrationState: MigrationToState? = MigrationToDeviceState.makeMigrationState() // audio recording and playback @Published var stopPreviousRecPlay: URL? = nil // coordinates currently playing source @Published var draft: ComposeState? diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index fd013f8339..365f23c33e 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -90,12 +90,12 @@ private func withBGTask(bgDelay: Double? = nil, f: @escaping () -> T) -> T { return r } -func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil) -> ChatResponse { +func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, _ ctrl: chat_ctrl? = nil) -> ChatResponse { logger.debug("chatSendCmd \(cmd.cmdType)") let start = Date.now let resp = bgTask - ? withBGTask(bgDelay: bgDelay) { sendSimpleXCmd(cmd) } - : sendSimpleXCmd(cmd) + ? withBGTask(bgDelay: bgDelay) { sendSimpleXCmd(cmd, ctrl) } + : sendSimpleXCmd(cmd, ctrl) logger.debug("chatSendCmd \(cmd.cmdType): \(resp.responseType)") if case let .response(_, json) = resp { logger.debug("chatSendCmd \(cmd.cmdType) response: \(json)") @@ -106,24 +106,24 @@ func chatSendCmdSync(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = return resp } -func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil) async -> ChatResponse { +func chatSendCmd(_ cmd: ChatCommand, bgTask: Bool = true, bgDelay: Double? = nil, _ ctrl: chat_ctrl? = nil) async -> ChatResponse { await withCheckedContinuation { cont in - cont.resume(returning: chatSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay)) + cont.resume(returning: chatSendCmdSync(cmd, bgTask: bgTask, bgDelay: bgDelay, ctrl)) } } -func chatRecvMsg() async -> ChatResponse? { +func chatRecvMsg(_ ctrl: chat_ctrl? = nil) async -> ChatResponse? { await withCheckedContinuation { cont in _ = withBGTask(bgDelay: msgDelay) { () -> ChatResponse? in - let resp = recvSimpleXMsg() + let resp = recvSimpleXMsg(ctrl) cont.resume(returning: resp) return resp } } } -func apiGetActiveUser() throws -> User? { - let r = chatSendCmdSync(.showActiveUser) +func apiGetActiveUser(ctrl: chat_ctrl? = nil) throws -> User? { + let r = chatSendCmdSync(.showActiveUser, ctrl) switch r { case let .activeUser(user): return user case .chatCmdError(_, .error(.noActiveUser)): return nil @@ -131,8 +131,8 @@ func apiGetActiveUser() throws -> User? { } } -func apiCreateActiveUser(_ p: Profile?, sameServers: Bool = false, pastTimestamp: Bool = false) throws -> User { - let r = chatSendCmdSync(.createActiveUser(profile: p, sameServers: sameServers, pastTimestamp: pastTimestamp)) +func apiCreateActiveUser(_ p: Profile?, sameServers: Bool = false, pastTimestamp: Bool = false, ctrl: chat_ctrl? = nil) throws -> User { + let r = chatSendCmdSync(.createActiveUser(profile: p, sameServers: sameServers, pastTimestamp: pastTimestamp), ctrl) if case let .activeUser(user) = r { return user } throw r } @@ -210,8 +210,8 @@ func apiDeleteUser(_ userId: Int64, _ delSMPQueues: Bool, viewPwd: String?) asyn throw r } -func apiStartChat() throws -> Bool { - let r = chatSendCmdSync(.startChat(mainApp: true)) +func apiStartChat(ctrl: chat_ctrl? = nil) throws -> Bool { + let r = chatSendCmdSync(.startChat(mainApp: true), ctrl) switch r { case .chatStarted: return true case .chatRunning: return false @@ -240,14 +240,14 @@ func apiSuspendChat(timeoutMicroseconds: Int) { logger.error("apiSuspendChat error: \(String(describing: r))") } -func apiSetTempFolder(tempFolder: String) throws { - let r = chatSendCmdSync(.setTempFolder(tempFolder: tempFolder)) +func apiSetTempFolder(tempFolder: String, ctrl: chat_ctrl? = nil) throws { + let r = chatSendCmdSync(.setTempFolder(tempFolder: tempFolder), ctrl) if case .cmdOk = r { return } throw r } -func apiSetFilesFolder(filesFolder: String) throws { - let r = chatSendCmdSync(.setFilesFolder(filesFolder: filesFolder)) +func apiSetFilesFolder(filesFolder: String, ctrl: chat_ctrl? = nil) throws { + let r = chatSendCmdSync(.setFilesFolder(filesFolder: filesFolder), ctrl) if case .cmdOk = r { return } throw r } @@ -258,6 +258,30 @@ func apiSetEncryptLocalFiles(_ enable: Bool) throws { throw r } +func apiSaveAppSettings(settings: AppSettings) throws { + let r = chatSendCmdSync(.apiSaveSettings(settings: settings)) + if case .cmdOk = r { return } + throw r +} + +func apiGetAppSettings(settings: AppSettings) throws -> AppSettings { + let r = chatSendCmdSync(.apiGetSettings(settings: settings)) + if case let .appSettings(settings) = r { return settings } + throw r +} + +func apiSetPQEncryption(_ enable: Bool) throws { + let r = chatSendCmdSync(.apiSetPQEncryption(enable: enable)) + if case .cmdOk = r { return } + throw r +} + +func apiSetContactPQ(_ contactId: Int64, _ enable: Bool) async throws -> Contact { + let r = await chatSendCmd(.apiSetContactPQ(contactId: contactId, enable: enable)) + if case let .contactPQAllowed(_, contact, _) = r { return contact } + throw r +} + func apiExportArchive(config: ArchiveConfig) async throws { try await sendCommandOkResp(.apiExportArchive(config: config)) } @@ -276,6 +300,10 @@ func apiStorageEncryption(currentKey: String = "", newKey: String = "") async th try await sendCommandOkResp(.apiStorageEncryption(config: DBEncryptionConfig(currentKey: currentKey, newKey: newKey))) } +func testStorageEncryption(key: String, _ ctrl: chat_ctrl? = nil) async throws { + try await sendCommandOkResp(.testStorageEncryption(key: key), ctrl) +} + func apiGetChats() throws -> [ChatData] { let userId = try currentUserId("apiGetChats") return try apiChatsResponse(chatSendCmdSync(.apiGetChats(userId: userId))) @@ -406,14 +434,14 @@ func apiDeleteMemberChatItem(groupId: Int64, groupMemberId: Int64, itemId: Int64 throw r } -func apiGetNtfToken() -> (DeviceToken?, NtfTknStatus?, NotificationsMode) { +func apiGetNtfToken() -> (DeviceToken?, NtfTknStatus?, NotificationsMode, String?) { let r = chatSendCmdSync(.apiGetNtfToken) switch r { - case let .ntfToken(token, status, ntfMode): return (token, status, ntfMode) - case .chatCmdError(_, .errorAgent(.CMD(.PROHIBITED))): return (nil, nil, .off) + case let .ntfToken(token, status, ntfMode, ntfServer): return (token, status, ntfMode, ntfServer) + case .chatCmdError(_, .errorAgent(.CMD(.PROHIBITED))): return (nil, nil, .off, nil) default: logger.debug("apiGetNtfToken response: \(String(describing: r))") - return (nil, nil, .off) + return (nil, nil, .off, nil) } } @@ -498,8 +526,8 @@ func getNetworkConfig() async throws -> NetCfg? { throw r } -func setNetworkConfig(_ cfg: NetCfg) throws { - let r = chatSendCmdSync(.apiSetNetworkConfig(networkConfig: cfg)) +func setNetworkConfig(_ cfg: NetCfg, ctrl: chat_ctrl? = nil) throws { + let r = chatSendCmdSync(.apiSetNetworkConfig(networkConfig: cfg), ctrl) if case .cmdOk = r { return } throw r } @@ -864,6 +892,36 @@ func apiChatUnread(type: ChatType, id: Int64, unreadChat: Bool) async throws { try await sendCommandOkResp(.apiChatUnread(type: type, id: id, unreadChat: unreadChat)) } +func uploadStandaloneFile(user: any UserLike, file: CryptoFile, ctrl: chat_ctrl? = nil) async -> (FileTransferMeta?, String?) { + let r = await chatSendCmd(.apiUploadStandaloneFile(userId: user.userId, file: file), ctrl) + if case let .sndStandaloneFileCreated(_, fileTransferMeta) = r { + return (fileTransferMeta, nil) + } else { + logger.error("uploadStandaloneFile error: \(String(describing: r))") + return (nil, String(describing: r)) + } +} + +func downloadStandaloneFile(user: any UserLike, url: String, file: CryptoFile, ctrl: chat_ctrl? = nil) async -> (RcvFileTransfer?, String?) { + let r = await chatSendCmd(.apiDownloadStandaloneFile(userId: user.userId, url: url, file: file), ctrl) + if case let .rcvStandaloneFileCreated(_, rcvFileTransfer) = r { + return (rcvFileTransfer, nil) + } else { + logger.error("downloadStandaloneFile error: \(String(describing: r))") + return (nil, String(describing: r)) + } +} + +func standaloneFileInfo(url: String, ctrl: chat_ctrl? = nil) async -> MigrationFileLinkData? { + let r = await chatSendCmd(.apiStandaloneFileInfo(url: url), ctrl) + if case let .standaloneFileInfo(fileMeta) = r { + return fileMeta + } else { + logger.error("standaloneFileInfo error: \(String(describing: r))") + return nil + } +} + func receiveFile(user: any UserLike, fileId: Int64, auto: Bool = false) async { if let chatItem = await apiReceiveFile(fileId: fileId, encrypted: privacyEncryptLocalFilesGroupDefault.get(), auto: auto) { await chatItemSimpleUpdate(user, chatItem) @@ -909,8 +967,8 @@ func cancelFile(user: User, fileId: Int64) async { } } -func apiCancelFile(fileId: Int64) async -> AChatItem? { - let r = await chatSendCmd(.cancelFile(fileId: fileId)) +func apiCancelFile(fileId: Int64, ctrl: chat_ctrl? = nil) async -> AChatItem? { + let r = await chatSendCmd(.cancelFile(fileId: fileId), ctrl) switch r { case let .sndFileCancelled(_, chatItem, _, _) : return chatItem case let .rcvFileCancelled(_, chatItem, _) : return chatItem @@ -1082,8 +1140,8 @@ func apiMarkChatItemRead(_ cInfo: ChatInfo, _ cItem: ChatItem) async { } } -private func sendCommandOkResp(_ cmd: ChatCommand) async throws { - let r = await chatSendCmd(cmd) +private func sendCommandOkResp(_ cmd: ChatCommand, _ ctrl: chat_ctrl? = nil) async throws { + let r = await chatSendCmd(cmd, ctrl) if case .cmdOk = r { return } throw r } @@ -1244,6 +1302,7 @@ func initializeChat(start: Bool, confirmStart: Bool = false, dbKey: String? = ni try apiSetTempFolder(tempFolder: getTempFilesDirectory().path) try apiSetFilesFolder(filesFolder: getAppFilesDirectory().path) try apiSetEncryptLocalFiles(privacyEncryptLocalFilesGroupDefault.get()) + try apiSetPQEncryption(pqExperimentalEnabledDefault.get()) m.chatInitialized = true m.currentUser = try apiGetActiveUser() if m.currentUser == nil { @@ -1302,7 +1361,7 @@ func startChat(refreshInvitations: Bool = true) throws { if (refreshInvitations) { try refreshCallInvitations() } - (m.savedToken, m.tokenStatus, m.notificationMode) = apiGetNtfToken() + (m.savedToken, m.tokenStatus, m.notificationMode, m.notificationServer) = apiGetNtfToken() // deviceToken is set when AppDelegate.application(didRegisterForRemoteNotificationsWithDeviceToken:) is called, // when it is called before startChat if let token = m.deviceToken { @@ -1323,6 +1382,16 @@ func startChat(refreshInvitations: Bool = true) throws { chatLastStartGroupDefault.set(Date.now) } +func startChatWithTemporaryDatabase(ctrl: chat_ctrl) throws -> User? { + logger.debug("startChatWithTemporaryDatabase") + let migrationActiveUser = try? apiGetActiveUser(ctrl: ctrl) ?? apiCreateActiveUser(Profile(displayName: "Temp", fullName: ""), ctrl: ctrl) + try setNetworkConfig(getNetCfg(), ctrl: ctrl) + try apiSetTempFolder(tempFolder: getMigrationTempFilesDirectory().path, ctrl: ctrl) + try apiSetFilesFolder(filesFolder: getMigrationTempFilesDirectory().path, ctrl: ctrl) + _ = try apiStartChat(ctrl: ctrl) + return migrationActiveUser +} + func changeActiveUser(_ userId: Int64, viewPwd: String?) { do { try changeActiveUser_(userId, viewPwd: viewPwd) @@ -1404,14 +1473,12 @@ class ChatReceiver { } func receiveMsgLoop() async { - // TODO use function that has timeout - if let msg = await chatRecvMsg() { - self._lastMsgTime = .now - await processReceivedMsg(msg) - } - if self.receiveMessages { + while self.receiveMessages { + if let msg = await chatRecvMsg() { + self._lastMsgTime = .now + await processReceivedMsg(msg) + } _ = try? await Task.sleep(nanoseconds: 7_500_000) - await receiveMsgLoop() } } @@ -1701,27 +1768,37 @@ func processReceivedMsg(_ res: ChatResponse) async { case let .rcvFileSndCancelled(user, aChatItem, _): await chatItemSimpleUpdate(user, aChatItem) Task { cleanupFile(aChatItem) } - case let .rcvFileProgressXFTP(user, aChatItem, _, _): - await chatItemSimpleUpdate(user, aChatItem) - case let .rcvFileError(user, aChatItem): - await chatItemSimpleUpdate(user, aChatItem) - Task { cleanupFile(aChatItem) } + case let .rcvFileProgressXFTP(user, aChatItem, _, _, _): + if let aChatItem = aChatItem { + await chatItemSimpleUpdate(user, aChatItem) + } + case let .rcvFileError(user, aChatItem, _): + if let aChatItem = aChatItem { + await chatItemSimpleUpdate(user, aChatItem) + Task { cleanupFile(aChatItem) } + } case let .sndFileStart(user, aChatItem, _): await chatItemSimpleUpdate(user, aChatItem) case let .sndFileComplete(user, aChatItem, _): await chatItemSimpleUpdate(user, aChatItem) Task { cleanupDirectFile(aChatItem) } case let .sndFileRcvCancelled(user, aChatItem, _): - await chatItemSimpleUpdate(user, aChatItem) - Task { cleanupDirectFile(aChatItem) } + if let aChatItem = aChatItem { + await chatItemSimpleUpdate(user, aChatItem) + Task { cleanupDirectFile(aChatItem) } + } case let .sndFileProgressXFTP(user, aChatItem, _, _, _): - await chatItemSimpleUpdate(user, aChatItem) + if let aChatItem = aChatItem { + await chatItemSimpleUpdate(user, aChatItem) + } case let .sndFileCompleteXFTP(user, aChatItem, _): await chatItemSimpleUpdate(user, aChatItem) Task { cleanupFile(aChatItem) } - case let .sndFileError(user, aChatItem): - await chatItemSimpleUpdate(user, aChatItem) - Task { cleanupFile(aChatItem) } + case let .sndFileError(user, aChatItem, _): + if let aChatItem = aChatItem { + await chatItemSimpleUpdate(user, aChatItem) + Task { cleanupFile(aChatItem) } + } case let .callInvitation(invitation): await MainActor.run { m.callInvitations[invitation.contact.id] = invitation @@ -1818,6 +1895,12 @@ func processReceivedMsg(_ res: ChatResponse) async { } } } + case let .contactPQEnabled(user, contact, _): + if active(user) { + await MainActor.run { + m.updateContact(contact) + } + } default: logger.debug("unsupported event: \(res.responseType)") } diff --git a/apps/ios/Shared/SimpleXApp.swift b/apps/ios/Shared/SimpleXApp.swift index e5b98589a0..7d69466c07 100644 --- a/apps/ios/Shared/SimpleXApp.swift +++ b/apps/ios/Shared/SimpleXApp.swift @@ -44,7 +44,12 @@ struct SimpleXApp: App { chatModel.appOpenUrl = url } .onAppear() { - if kcAppPassword.get() == nil || kcSelfDestructPassword.get() == nil { + // Present screen for continue migration if it wasn't finished yet + if chatModel.migrationState != nil { + // It's important, otherwise, user may be locked in undefined state + onboardingStageDefault.set(.step1_SimpleXInfo) + chatModel.onboardingStage = onboardingStageDefault.get() + } else if kcAppPassword.get() == nil || kcSelfDestructPassword.get() == nil { DispatchQueue.main.asyncAfter(deadline: .now() + 0.15) { initChatAndMigrate() } diff --git a/apps/ios/Shared/Views/Call/ActiveCallView.swift b/apps/ios/Shared/Views/Call/ActiveCallView.swift index a3be2e900a..9f246f63f3 100644 --- a/apps/ios/Shared/Views/Call/ActiveCallView.swift +++ b/apps/ios/Shared/Views/Call/ActiveCallView.swift @@ -12,49 +12,67 @@ import SimpleXChat struct ActiveCallView: View { @EnvironmentObject var m: ChatModel + @Environment(\.colorScheme) var colorScheme @ObservedObject var call: Call @Environment(\.scenePhase) var scenePhase @State private var client: WebRTCClient? = nil @State private var activeCall: WebRTCClient.Call? = nil @State private var localRendererAspectRatio: CGFloat? = nil @Binding var canConnectCall: Bool + @State var prevColorScheme: ColorScheme = .dark + @State var pipShown = false var body: some View { - ZStack(alignment: .bottom) { - if let client = client, [call.peerMedia, call.localMedia].contains(.video), activeCall != nil { - GeometryReader { g in - let width = g.size.width * 0.3 - ZStack(alignment: .topTrailing) { - CallViewRemote(client: client, activeCall: $activeCall) - CallViewLocal(client: client, activeCall: $activeCall, localRendererAspectRatio: $localRendererAspectRatio) - .cornerRadius(10) - .frame(width: width, height: width / (localRendererAspectRatio ?? 1)) - .padding([.top, .trailing], 17) + ZStack(alignment: .topLeading) { + ZStack(alignment: .bottom) { + if let client = client, [call.peerMedia, call.localMedia].contains(.video), activeCall != nil { + GeometryReader { g in + let width = g.size.width * 0.3 + ZStack(alignment: .topTrailing) { + CallViewRemote(client: client, activeCall: $activeCall, activeCallViewIsCollapsed: $m.activeCallViewIsCollapsed, pipShown: $pipShown) + CallViewLocal(client: client, activeCall: $activeCall, localRendererAspectRatio: $localRendererAspectRatio, pipShown: $pipShown) + .cornerRadius(10) + .frame(width: width, height: width / (localRendererAspectRatio ?? 1)) + .padding([.top, .trailing], 17) + ZStack(alignment: .center) { + // For some reason, when the view in GeometryReader and ZStack is visible, it steals clicks on a back button, so showing something on top like this with background color helps (.clear color doesn't work) + } + .frame(maxWidth: .infinity, maxHeight: .infinity) + .background(Color.primary.opacity(0.000001)) + } } } - } - if let call = m.activeCall, let client = client { - ActiveCallOverlay(call: call, client: client) + if let call = m.activeCall, let client = client, (!pipShown || !call.supportsVideo) { + ActiveCallOverlay(call: call, client: client) + } } } + .allowsHitTesting(!m.activeCallViewIsCollapsed) + .opacity(m.activeCallViewIsCollapsed ? 0 : 1) .onAppear { logger.debug("ActiveCallView: appear client is nil \(client == nil), scenePhase \(String(describing: scenePhase)), canConnectCall \(canConnectCall)") AppDelegate.keepScreenOn(true) createWebRTCClient() dismissAllSheets() + hideKeyboard() + prevColorScheme = colorScheme } .onChange(of: canConnectCall) { _ in logger.debug("ActiveCallView: canConnectCall changed to \(canConnectCall)") createWebRTCClient() } + .onChange(of: m.activeCallViewIsCollapsed) { _ in + hideKeyboard() + } .onDisappear { logger.debug("ActiveCallView: disappear") Task { await m.callCommand.setClient(nil) } AppDelegate.keepScreenOn(false) client?.endCall() } - .background(.black) - .preferredColorScheme(.dark) + .background(m.activeCallViewIsCollapsed ? .clear : .black) + // Quite a big delay when opening/closing the view when a scheme changes (globally) this way. It's not needed when CallKit is used since status bar is green with white text on it + .preferredColorScheme(m.activeCallViewIsCollapsed || CallController.useCallKit() ? prevColorScheme : .dark) } private func createWebRTCClient() { @@ -69,8 +87,8 @@ struct ActiveCallView: View { @MainActor private func processRtcMessage(msg: WVAPIMessage) { if call == m.activeCall, - let call = m.activeCall, - let client = client { + let call = m.activeCall, + let client = client { logger.debug("ActiveCallView: response \(msg.resp.respType)") switch msg.resp { case let .capabilities(capabilities): @@ -90,7 +108,7 @@ struct ActiveCallView: View { Task { do { try await apiSendCallOffer(call.contact, offer, iceCandidates, - media: call.localMedia, capabilities: capabilities) + media: call.localMedia, capabilities: capabilities) } catch { logger.error("apiSendCallOffer \(responseError(error))") } @@ -122,13 +140,15 @@ struct ActiveCallView: View { if let callStatus = WebRTCCallStatus.init(rawValue: state.connectionState), case .connected = callStatus { call.direction == .outgoing - ? CallController.shared.reportOutgoingCall(call: call, connectedAt: nil) - : CallController.shared.reportIncomingCall(call: call, connectedAt: nil) + ? CallController.shared.reportOutgoingCall(call: call, connectedAt: nil) + : CallController.shared.reportIncomingCall(call: call, connectedAt: nil) call.callState = .connected + call.connectedAt = .now } if state.connectionState == "closed" { closeCallView(client) m.activeCall = nil + m.activeCallViewIsCollapsed = false } Task { do { @@ -140,6 +160,7 @@ struct ActiveCallView: View { case let .connected(connectionInfo): call.callState = .connected call.connectionInfo = connectionInfo + call.connectedAt = .now case .ended: closeCallView(client) call.callState = .ended @@ -153,6 +174,7 @@ struct ActiveCallView: View { case .end: closeCallView(client) m.activeCall = nil + m.activeCallViewIsCollapsed = false default: () } case let .error(message): @@ -181,7 +203,7 @@ struct ActiveCallOverlay: View { VStack { switch call.localMedia { case .video: - callInfoView(call, .leading) + videoCallInfoView(call) .foregroundColor(.white) .opacity(0.8) .padding() @@ -208,16 +230,25 @@ struct ActiveCallOverlay: View { .frame(maxWidth: .infinity, alignment: .center) case .audio: - VStack { - ProfileImage(imageStr: call.contact.profile.image) - .scaledToFit() - .frame(width: 192, height: 192) - callInfoView(call, .center) + ZStack(alignment: .topLeading) { + Button { + chatModel.activeCallViewIsCollapsed = true + } label: { + Label("Back", systemImage: "chevron.left") + .padding() + .foregroundColor(.white.opacity(0.8)) + } + VStack { + ProfileImage(imageStr: call.contact.profile.image) + .scaledToFit() + .frame(width: 192, height: 192) + audioCallInfoView(call) + } + .foregroundColor(.white) + .opacity(0.8) + .padding() + .frame(maxHeight: .infinity) } - .foregroundColor(.white) - .opacity(0.8) - .padding() - .frame(maxHeight: .infinity) Spacer() @@ -235,12 +266,12 @@ struct ActiveCallOverlay: View { .frame(maxWidth: .infinity) } - private func callInfoView(_ call: Call, _ alignment: Alignment) -> some View { + private func audioCallInfoView(_ call: Call) -> some View { VStack { Text(call.contact.chatViewName) .lineLimit(1) .font(.title) - .frame(maxWidth: .infinity, alignment: alignment) + .frame(maxWidth: .infinity, alignment: .center) Group { Text(call.callState.text) HStack { @@ -251,7 +282,36 @@ struct ActiveCallOverlay: View { } } .font(.subheadline) - .frame(maxWidth: .infinity, alignment: alignment) + .frame(maxWidth: .infinity, alignment: .center) + } + } + + private func videoCallInfoView(_ call: Call) -> some View { + VStack { + Button { + chatModel.activeCallViewIsCollapsed = true + } label: { + HStack(alignment: .center, spacing: 16) { + Image(systemName: "chevron.left") + .resizable() + .frame(width: 10, height: 18) + Text(call.contact.chatViewName) + .lineLimit(1) + .font(.title) + .frame(maxWidth: .infinity, alignment: .leading) + } + } + Group { + Text(call.callState.text) + HStack { + Text(call.encryptionStatus) + if let connInfo = call.connectionInfo { + Text("(") + Text(connInfo.text) + Text(")") + } + } + } + .font(.subheadline) + .frame(maxWidth: .infinity, alignment: .leading) } } diff --git a/apps/ios/Shared/Views/Call/CallManager.swift b/apps/ios/Shared/Views/Call/CallManager.swift index 194af3ab01..a6d5ea17c4 100644 --- a/apps/ios/Shared/Views/Call/CallManager.swift +++ b/apps/ios/Shared/Views/Call/CallManager.swift @@ -92,6 +92,7 @@ class CallManager { if case .ended = call.callState { logger.debug("CallManager.endCall: call ended") m.activeCall = nil + m.activeCallViewIsCollapsed = false m.showCallView = false completed() } else { @@ -100,6 +101,7 @@ class CallManager { await m.callCommand.processCommand(.end) await MainActor.run { m.activeCall = nil + m.activeCallViewIsCollapsed = false m.showCallView = false completed() } diff --git a/apps/ios/Shared/Views/Call/CallViewRenderers.swift b/apps/ios/Shared/Views/Call/CallViewRenderers.swift index 93766ced1c..a3201d9351 100644 --- a/apps/ios/Shared/Views/Call/CallViewRenderers.swift +++ b/apps/ios/Shared/Views/Call/CallViewRenderers.swift @@ -6,14 +6,20 @@ import SwiftUI import WebRTC import SimpleXChat +import AVKit struct CallViewRemote: UIViewRepresentable { var client: WebRTCClient var activeCall: Binding + @State var enablePip: (Bool) -> Void = {_ in } + @Binding var activeCallViewIsCollapsed: Bool + @Binding var pipShown: Bool - init(client: WebRTCClient, activeCall: Binding) { + init(client: WebRTCClient, activeCall: Binding, activeCallViewIsCollapsed: Binding, pipShown: Binding) { self.client = client self.activeCall = activeCall + self._activeCallViewIsCollapsed = activeCallViewIsCollapsed + self._pipShown = pipShown } func makeUIView(context: Context) -> UIView { @@ -23,12 +29,120 @@ struct CallViewRemote: UIViewRepresentable { remoteRenderer.videoContentMode = .scaleAspectFill client.addRemoteRenderer(call, remoteRenderer) addSubviewAndResize(remoteRenderer, into: view) + + if AVPictureInPictureController.isPictureInPictureSupported() { + makeViewWithRTCRenderer(call, remoteRenderer, view, context) + } } return view } + + func makeViewWithRTCRenderer(_ call: WebRTCClient.Call, _ remoteRenderer: RTCMTLVideoView, _ view: UIView, _ context: Context) { + let pipRemoteRenderer = RTCMTLVideoView(frame: view.frame) + pipRemoteRenderer.videoContentMode = .scaleAspectFill + + let pipVideoCallViewController = AVPictureInPictureVideoCallViewController() + pipVideoCallViewController.preferredContentSize = CGSize(width: 1080, height: 1920) + addSubviewAndResize(pipRemoteRenderer, into: pipVideoCallViewController.view) + let pipContentSource = AVPictureInPictureController.ContentSource( + activeVideoCallSourceView: view, + contentViewController: pipVideoCallViewController + ) + + let pipController = AVPictureInPictureController(contentSource: pipContentSource) + pipController.canStartPictureInPictureAutomaticallyFromInline = true + pipController.delegate = context.coordinator + context.coordinator.pipController = pipController + context.coordinator.willShowHide = { show in + if show { + client.addRemoteRenderer(call, pipRemoteRenderer) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { + activeCallViewIsCollapsed = true + } + } else { + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { + activeCallViewIsCollapsed = false + } + } + } + context.coordinator.didShowHide = { show in + if show { + remoteRenderer.isHidden = true + } else { + client.removeRemoteRenderer(call, pipRemoteRenderer) + remoteRenderer.isHidden = false + } + pipShown = show + } + DispatchQueue.main.async { + enablePip = { enable in + if enable != pipShown /* pipController.isPictureInPictureActive */ { + if enable { + pipController.startPictureInPicture() + } else { + pipController.stopPictureInPicture() + } + } + } + } + } + + func makeCoordinator() -> Coordinator { + Coordinator() + } func updateUIView(_ view: UIView, context: Context) { logger.debug("CallView.updateUIView remote") + DispatchQueue.main.async { + if activeCallViewIsCollapsed != pipShown { + enablePip(activeCallViewIsCollapsed) + } + } + } + + // MARK: - Coordinator + class Coordinator: NSObject, AVPictureInPictureControllerDelegate { + var pipController: AVPictureInPictureController? = nil + var willShowHide: (Bool) -> Void = { _ in } + var didShowHide: (Bool) -> Void = { _ in } + + func pictureInPictureControllerWillStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + willShowHide(true) + } + + func pictureInPictureControllerDidStartPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + didShowHide(true) + } + + func pictureInPictureController(_ pictureInPictureController: AVPictureInPictureController, failedToStartPictureInPictureWithError error: Error) { + logger.error("PiP failed to start: \(error.localizedDescription)") + } + + func pictureInPictureControllerWillStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + willShowHide(false) + } + + func pictureInPictureControllerDidStopPictureInPicture(_ pictureInPictureController: AVPictureInPictureController) { + didShowHide(false) + } + + deinit { + pipController?.stopPictureInPicture() + pipController?.canStartPictureInPictureAutomaticallyFromInline = false + pipController?.contentSource = nil + pipController?.delegate = nil + pipController = nil + } + } + + class SampleBufferVideoCallView: UIView { + override class var layerClass: AnyClass { + get { return AVSampleBufferDisplayLayer.self } + } + + var sampleBufferDisplayLayer: AVSampleBufferDisplayLayer { + return layer as! AVSampleBufferDisplayLayer + } } } @@ -36,11 +150,14 @@ struct CallViewLocal: UIViewRepresentable { var client: WebRTCClient var activeCall: Binding var localRendererAspectRatio: Binding + @State var pipStateChanged: (Bool) -> Void = {_ in } + @Binding var pipShown: Bool - init(client: WebRTCClient, activeCall: Binding, localRendererAspectRatio: Binding) { + init(client: WebRTCClient, activeCall: Binding, localRendererAspectRatio: Binding, pipShown: Binding) { self.client = client self.activeCall = activeCall self.localRendererAspectRatio = localRendererAspectRatio + self._pipShown = pipShown } func makeUIView(context: Context) -> UIView { @@ -50,12 +167,18 @@ struct CallViewLocal: UIViewRepresentable { client.addLocalRenderer(call, localRenderer) client.startCaptureLocalVideo(call) addSubviewAndResize(localRenderer, into: view) + DispatchQueue.main.async { + pipStateChanged = { shown in + localRenderer.isHidden = shown + } + } } return view } func updateUIView(_ view: UIView, context: Context) { logger.debug("CallView.updateUIView local") + pipStateChanged(pipShown) } } diff --git a/apps/ios/Shared/Views/Call/WebRTC.swift b/apps/ios/Shared/Views/Call/WebRTC.swift index c21ef5019a..919b1e14e7 100644 --- a/apps/ios/Shared/Views/Call/WebRTC.swift +++ b/apps/ios/Shared/Views/Call/WebRTC.swift @@ -28,6 +28,7 @@ class Call: ObservableObject, Equatable { @Published var speakerEnabled = false @Published var videoEnabled: Bool @Published var connectionInfo: ConnectionInfo? + @Published var connectedAt: Date? = nil init( direction: CallDirection, @@ -59,6 +60,7 @@ class Call: ObservableObject, Equatable { } } var hasMedia: Bool { get { callState == .offerSent || callState == .negotiated || callState == .connected } } + var supportsVideo: Bool { get { peerMedia == .video || localMedia == .video } } } enum CallDirection { diff --git a/apps/ios/Shared/Views/Call/WebRTCClient.swift b/apps/ios/Shared/Views/Call/WebRTCClient.swift index 933a3c745e..1806984d64 100644 --- a/apps/ios/Shared/Views/Call/WebRTCClient.swift +++ b/apps/ios/Shared/Views/Call/WebRTCClient.swift @@ -331,6 +331,10 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg activeCall.remoteStream?.add(renderer) } + func removeRemoteRenderer(_ activeCall: Call, _ renderer: RTCVideoRenderer) { + activeCall.remoteStream?.remove(renderer) + } + func startCaptureLocalVideo(_ activeCall: Call) { #if targetEnvironment(simulator) guard @@ -410,6 +414,7 @@ final class WebRTCClient: NSObject, RTCVideoViewDelegate, RTCFrameEncryptorDeleg guard let call = activeCall.wrappedValue else { return } logger.debug("WebRTCClient: ending the call") activeCall.wrappedValue = nil + (call.localCamera as? RTCCameraVideoCapturer)?.stopCapture() call.connection.close() call.connection.delegate = nil call.frameEncryptor?.delegate = nil diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index b702c2cc23..8b60fc7649 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -103,6 +103,7 @@ struct ChatInfoView: View { @State private var sendReceipts = SendReceipts.userDefault(true) @State private var sendReceiptsUserDefault = true @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false + @AppStorage(GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED, store: groupDefaults) private var pqExperimentalEnabled = false enum ChatInfoViewAlert: Identifiable { case clearChatAlert @@ -110,6 +111,7 @@ struct ChatInfoView: View { case switchAddressAlert case abortSwitchAddressAlert case syncConnectionForceAlert + case allowContactPQEncryptionAlert case error(title: LocalizedStringKey, error: LocalizedStringKey = "") var id: String { @@ -119,6 +121,7 @@ struct ChatInfoView: View { case .switchAddressAlert: return "switchAddressAlert" case .abortSwitchAddressAlert: return "abortSwitchAddressAlert" case .syncConnectionForceAlert: return "syncConnectionForceAlert" + case .allowContactPQEncryptionAlert: return "allowContactPQEncryptionAlert" case let .error(title, _): return "error \(title)" } } @@ -165,6 +168,22 @@ struct ChatInfoView: View { } .disabled(!contact.ready || !contact.active) + if pqExperimentalEnabled, + let conn = contact.activeConn { + Section { + infoRow(Text(String("E2E encryption")), conn.connPQEnabled ? "Quantum resistant" : "Standard") + if !conn.pqEncryption { + allowPQButton() + } + } header: { + Text(String("Quantum resistant E2E encryption")) + } footer: { + if !conn.pqEncryption { + Text(String("After allowing quantum resistant encryption, it will be enabled after several messages if your contact also allows it.")) + } + } + } + if let contactLink = contact.contactLink { Section { SimpleXLinkQRCode(uri: contactLink) @@ -237,6 +256,7 @@ struct ChatInfoView: View { case .switchAddressAlert: return switchAddressAlert(switchContactAddress) case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchContactAddress) case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncContactConnection(force: true) }) + case .allowContactPQEncryptionAlert: return allowContactPQEncryptionAlert() case let .error(title, error): return mkAlert(title: title, message: error) } } @@ -410,6 +430,15 @@ struct ChatInfoView: View { } } + private func allowPQButton() -> some View { + Button { + alert = .allowContactPQEncryptionAlert + } label: { + Label(String("Allow PQ encryption"), systemImage: "exclamationmark.triangle") + .foregroundColor(.orange) + } + } + private func networkStatusRow() -> some View { HStack { Text("Network status") @@ -543,6 +572,34 @@ struct ChatInfoView: View { } } } + + private func allowContactPQEncryption() { + Task { + do { + let ct = try await apiSetContactPQ(contact.apiId, true) + contact = ct + await MainActor.run { + chatModel.updateContact(ct) + dismiss() + } + } catch let error { + logger.error("allowContactPQEncryption apiSetContactPQ error: \(responseError(error))") + let a = getErrorAlert(error, "Error allowing contact PQ encryption") + await MainActor.run { + alert = .error(title: a.title, error: a.message) + } + } + } + } + + func allowContactPQEncryptionAlert() -> Alert { + Alert( + title: Text(String("Allow quantum resistant encryption?")), + message: Text(String("This is an experimental feature, it is not recommended to enable it for important chats.")), + primaryButton: .destructive(Text(String("Allow")), action: allowContactPQEncryption), + secondaryButton: .cancel() + ) + } } func switchAddressAlert(_ switchAddress: @escaping () -> Void) -> Alert { diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift index c7e89fc5ed..c3e4805bf3 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIImageView.swift @@ -29,6 +29,9 @@ struct CIImageView: View { FullScreenMediaView(chatItem: chatItem, image: uiImage, showView: $showFullScreenImage, scrollProxy: scrollProxy) } .onTapGesture { showFullScreenImage = true } + .onChange(of: m.activeCallViewIsCollapsed) { _ in + showFullScreenImage = false + } } else if let data = Data(base64Encoded: dropImagePrefix(image)), let uiImage = UIImage(data: data) { imageView(uiImage) diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift index a824ddc49f..ff208fe58a 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIVideoView.swift @@ -120,6 +120,9 @@ struct CIVideoView: View { showFullScreenPlayer = urlDecrypted != nil } } + .onChange(of: m.activeCallViewIsCollapsed) { _ in + showFullScreenPlayer = false + } if !decryptionInProgress { Button { decrypt(file: file) { @@ -168,6 +171,9 @@ struct CIVideoView: View { default: () } } + .onChange(of: m.activeCallViewIsCollapsed) { _ in + showFullScreenPlayer = false + } if !videoPlaying { Button { m.stopPreviousRecPlay = url diff --git a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift index f7775a7cdd..3475e7a8b6 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/FramedItemView.swift @@ -253,6 +253,7 @@ struct FramedItemView: View { ciQuotedMsgTextView(qi, lines: 3) } } + .fixedSize(horizontal: false, vertical: true) .padding(.top, 6) .padding(.horizontal, 12) } diff --git a/apps/ios/Shared/Views/Chat/ChatItemView.swift b/apps/ios/Shared/Views/Chat/ChatItemView.swift index 8f67a8f737..da9dc523e1 100644 --- a/apps/ios/Shared/Views/Chat/ChatItemView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItemView.swift @@ -110,6 +110,10 @@ struct ChatItemContentView: View { case .sndModerated: deletedItemView() case .rcvModerated: deletedItemView() case .rcvBlocked: deletedItemView() + case let .sndDirectE2EEInfo(e2eeInfo): CIEventView(eventText: directE2EEInfoText(e2eeInfo)) + case let .rcvDirectE2EEInfo(e2eeInfo): CIEventView(eventText: directE2EEInfoText(e2eeInfo)) + case .sndGroupE2EEInfo: CIEventView(eventText: e2eeInfoNoPQText()) + case .rcvGroupE2EEInfo: CIEventView(eventText: e2eeInfoNoPQText()) case let .invalidJSON(json): CIInvalidJSONView(json: json) } } @@ -170,6 +174,22 @@ struct ChatItemContentView: View { Text(members) } } + + private func directE2EEInfoText(_ info: E2EEInfo) -> Text { + info.pqEnabled + ? Text("Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery.") + .font(.caption) + .foregroundColor(.secondary) + .fontWeight(.light) + : e2eeInfoNoPQText() + } + + private func e2eeInfoNoPQText() -> Text { + Text("Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery.") + .font(.caption) + .foregroundColor(.secondary) + .fontWeight(.light) + } } func chatEventText(_ text: Text) -> Text { diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index 35caf655e9..468027ec8a 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -161,11 +161,15 @@ struct ChatView: View { HStack { let callsPrefEnabled = contact.mergedPreferences.calls.enabled.forUser if callsPrefEnabled { - callButton(contact, .audio, imageName: "phone") - .disabled(!contact.ready || !contact.active) + if chatModel.activeCall == nil { + callButton(contact, .audio, imageName: "phone") + .disabled(!contact.ready || !contact.active) + } else if let call = chatModel.activeCall, call.contact.id == cInfo.id { + endCallButton(call) + } } Menu { - if callsPrefEnabled { + if callsPrefEnabled && chatModel.activeCall == nil { Button { CallController.shared.startCall(contact, .video) } label: { @@ -422,7 +426,19 @@ struct ChatView: View { Image(systemName: imageName) } } - + + private func endCallButton(_ call: Call) -> some View { + Button { + if let uuid = call.callkitUUID { + CallController.shared.endCall(callUUID: uuid) + } else { + CallController.shared.endCall(call: call) {} + } + } label: { + Image(systemName: "phone.down.fill").tint(.red) + } + } + private func searchButton() -> some View { Button { searchMode = true @@ -541,7 +557,12 @@ struct ChatView: View { chatItemView(ci, nil, prev) } } else { - chatItemView(chatItem, range, prevItem) + // Switch branches just to work around context menu problem when 'revealed' changes but size of item isn't + if revealed { + chatItemView(chatItem, range, prevItem) + } else { + chatItemView(chatItem, range, prevItem) + } } } } @@ -630,7 +651,7 @@ struct ChatView: View { playbackState: $playbackState, playbackTime: $playbackTime ) - .uiKitContextMenu(menu: uiMenu, allowMenu: $allowMenu) + .uiKitContextMenu(maxWidth: maxWidth, menu: uiMenu, allowMenu: $allowMenu) .accessibilityLabel("") if ci.content.msgContent != nil && (ci.meta.itemDeleted == nil || revealed) && ci.reactions.count > 0 { chatItemReactions(ci) diff --git a/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift b/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift index 57007fff3f..86acbf6d54 100644 --- a/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/ContactPreferencesView.swift @@ -35,7 +35,7 @@ struct ContactPreferencesView: View { .disabled(currentFeaturesAllowed == featuresAllowed) } } - .modifier(BackButton { + .modifier(BackButton(disabled: Binding.constant(false)) { if currentFeaturesAllowed == featuresAllowed { dismiss() } else { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift index dbea6a17e0..88b36077b4 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupChatInfoView.swift @@ -234,39 +234,29 @@ struct GroupChatInfoView: View { Spacer() memberInfo(member) } - - // revert from this: + if user { v - } else if member.canBeRemoved(groupInfo: groupInfo) { - removeSwipe(member, blockSwipe(member, v)) + } else if groupInfo.membership.memberRole >= .admin { + // TODO if there are more actions, refactor with lists of swipeActions + let canBlockForAll = member.canBlockForAll(groupInfo: groupInfo) + let canRemove = member.canBeRemoved(groupInfo: groupInfo) + if canBlockForAll && canRemove { + removeSwipe(member, blockForAllSwipe(member, v)) + } else if canBlockForAll { + blockForAllSwipe(member, v) + } else if canRemove { + removeSwipe(member, v) + } else { + v + } } else { - blockSwipe(member, v) + if !member.blockedByAdmin { + blockSwipe(member, v) + } else { + v + } } - // revert to this: vvv -// if user { -// v -// } else if groupInfo.membership.memberRole >= .admin { -// // TODO if there are more actions, refactor with lists of swipeActions -// let canBlockForAll = member.canBlockForAll(groupInfo: groupInfo) -// let canRemove = member.canBeRemoved(groupInfo: groupInfo) -// if canBlockForAll && canRemove { -// removeSwipe(member, blockForAllSwipe(member, v)) -// } else if canBlockForAll { -// blockForAllSwipe(member, v) -// } else if canRemove { -// removeSwipe(member, v) -// } else { -// v -// } -// } else { -// if !member.blockedByAdmin { -// blockSwipe(member, v) -// } else { -// v -// } -// } - // ^^^ } @ViewBuilder private func memberInfo(_ member: GroupMember) -> some View { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index d2b0f77393..999617dde7 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -168,24 +168,11 @@ struct GroupMemberInfoView: View { } } - // revert from this: - Section { - if member.memberSettings.showMessages { - blockMemberButton(member) - } else { - unblockMemberButton(member) - } - if member.canBeRemoved(groupInfo: groupInfo) { - removeMemberButton(member) - } + if groupInfo.membership.memberRole >= .admin { + adminDestructiveSection(member) + } else { + nonAdminBlockSection(member) } - // revert to this: vvv -// if groupInfo.membership.memberRole >= .admin { -// adminDestructiveSection(member) -// } else { -// nonAdminBlockSection(member) -// } - // ^^^ if developerTools { Section("For console") { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift index d88bdfa4a4..7ab4bf4ece 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift @@ -48,7 +48,7 @@ struct GroupPreferencesView: View { preferences.timedMessages.ttl = currentPreferences.timedMessages.ttl } } - .modifier(BackButton { + .modifier(BackButton(disabled: Binding.constant(false)) { if currentPreferences == preferences { dismiss() } else { diff --git a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift index d6dbf06efc..00d4f8c37b 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupWelcomeView.swift @@ -24,7 +24,7 @@ struct GroupWelcomeView: View { VStack { if groupInfo.canEdit { editorView() - .modifier(BackButton { + .modifier(BackButton(disabled: Binding.constant(false)) { if welcomeTextUnchanged() { dismiss() } else { diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 22807f6182..38aabdc21d 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -264,7 +264,9 @@ struct ChatListView: View { } func filtered(_ chat: Chat) -> Bool { - (chat.chatInfo.chatSettings?.favorite ?? false) || chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat + (chat.chatInfo.chatSettings?.favorite ?? false) || + chat.chatStats.unreadChat || + (chat.chatInfo.ntfsEnabled && chat.chatStats.unreadCount > 0) } func viewNameContains(_ cInfo: ChatInfo, _ s: String) -> Bool { diff --git a/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift b/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift index 90cd17fbb3..4031c3e00a 100644 --- a/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseEncryptionView.swift @@ -36,6 +36,7 @@ enum DatabaseEncryptionAlert: Identifiable { struct DatabaseEncryptionView: View { @EnvironmentObject private var m: ChatModel @Binding var useKeychain: Bool + var migration: Bool @State private var alert: DatabaseEncryptionAlert? = nil @State private var progressIndicator = false @State private var useKeychainToggle = storeDBPassphraseGroupDefault.get() @@ -48,7 +49,12 @@ struct DatabaseEncryptionView: View { var body: some View { ZStack { - databaseEncryptionView() + List { + if migration { + chatStoppedView() + } + databaseEncryptionView() + } if progressIndicator { ProgressView().scaleEffect(2) } @@ -56,72 +62,71 @@ struct DatabaseEncryptionView: View { } private func databaseEncryptionView() -> some View { - List { - Section { - settingsRow(storedKey ? "key.fill" : "key", color: storedKey ? .green : .secondary) { - Toggle("Save passphrase in Keychain", isOn: $useKeychainToggle) + Section { + settingsRow(storedKey ? "key.fill" : "key", color: storedKey ? .green : .secondary) { + Toggle("Save passphrase in Keychain", isOn: $useKeychainToggle) .onChange(of: useKeychainToggle) { _ in if useKeychainToggle { setUseKeychain(true) - } else if storedKey { + } else if storedKey && !migration { + // Don't show in migration process since it will remove the key after successfull encryption alert = .keychainRemoveKey } else { setUseKeychain(false) } } - .disabled(initialRandomDBPassphrase) - } + .disabled(initialRandomDBPassphrase && !migration) + } - if !initialRandomDBPassphrase && m.chatDbEncrypted == true { - PassphraseField(key: $currentKey, placeholder: "Current passphrase…", valid: validKey(currentKey)) - } + if !initialRandomDBPassphrase && m.chatDbEncrypted == true { + PassphraseField(key: $currentKey, placeholder: "Current passphrase…", valid: validKey(currentKey)) + } - PassphraseField(key: $newKey, placeholder: "New passphrase…", valid: validKey(newKey), showStrength: true) - PassphraseField(key: $confirmNewKey, placeholder: "Confirm new passphrase…", valid: confirmNewKey == "" || newKey == confirmNewKey) + PassphraseField(key: $newKey, placeholder: "New passphrase…", valid: validKey(newKey), showStrength: true) + PassphraseField(key: $confirmNewKey, placeholder: "Confirm new passphrase…", valid: confirmNewKey == "" || newKey == confirmNewKey) - settingsRow("lock.rotation") { - Button("Update database passphrase") { - alert = currentKey == "" - ? (useKeychain ? .encryptDatabaseSaved : .encryptDatabase) - : (useKeychain ? .changeDatabaseKeySaved : .changeDatabaseKey) - } + settingsRow("lock.rotation") { + Button(migration ? "Set passphrase" : "Update database passphrase") { + alert = currentKey == "" + ? (useKeychain ? .encryptDatabaseSaved : .encryptDatabase) + : (useKeychain ? .changeDatabaseKeySaved : .changeDatabaseKey) } - .disabled( - (m.chatDbEncrypted == true && currentKey == "") || - currentKey == newKey || - newKey != confirmNewKey || - newKey == "" || - !validKey(currentKey) || - !validKey(newKey) - ) - } header: { - Text("") - } footer: { - VStack(alignment: .leading, spacing: 16) { - if m.chatDbEncrypted == false { - Text("Your chat database is not encrypted - set passphrase to encrypt it.") - } else if useKeychain { - if storedKey { - Text("iOS Keychain is used to securely store passphrase - it allows receiving push notifications.") - if initialRandomDBPassphrase { - Text("Database is encrypted using a random passphrase, you can change it.") - } else { - Text("**Please note**: you will NOT be able to recover or change passphrase if you lose it.") - } + } + .disabled( + (m.chatDbEncrypted == true && currentKey == "") || + currentKey == newKey || + newKey != confirmNewKey || + newKey == "" || + !validKey(currentKey) || + !validKey(newKey) + ) + } header: { + Text(migration ? "Database passphrase" : "") + } footer: { + VStack(alignment: .leading, spacing: 16) { + if m.chatDbEncrypted == false { + Text("Your chat database is not encrypted - set passphrase to encrypt it.") + } else if useKeychain { + if storedKey { + Text("iOS Keychain is used to securely store passphrase - it allows receiving push notifications.") + if initialRandomDBPassphrase && !migration { + Text("Database is encrypted using a random passphrase, you can change it.") } else { - Text("iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications.") + Text("**Please note**: you will NOT be able to recover or change passphrase if you lose it.") } } else { - Text("You have to enter passphrase every time the app starts - it is not stored on the device.") - Text("**Please note**: you will NOT be able to recover or change passphrase if you lose it.") - if m.notificationMode == .instant && m.notificationPreview != .hidden { - Text("**Warning**: Instant push notifications require passphrase saved in Keychain.") - } + Text("iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications.") + } + } else { + Text("You have to enter passphrase every time the app starts - it is not stored on the device.") + Text("**Please note**: you will NOT be able to recover or change passphrase if you lose it.") + if m.notificationMode == .instant && m.notificationPreview != .hidden && !migration { + Text("**Warning**: Instant push notifications require passphrase saved in Keychain.") } } - .padding(.top, 1) - .font(.callout) } + .padding(.top, 1) + .font(.callout) } .onAppear { if initialRandomDBPassphrase { currentKey = kcDatabasePassword.get() ?? "" } @@ -136,9 +141,15 @@ struct DatabaseEncryptionView: View { do { encryptionStartedDefault.set(true) encryptionStartedAtDefault.set(Date.now) + if !m.chatDbChanged { + try apiSaveAppSettings(settings: AppSettings.current.prepareForExport()) + } try await apiStorageEncryption(currentKey: currentKey, newKey: newKey) encryptionStartedDefault.set(false) initialRandomDBPassphraseGroupDefault.set(false) + if migration { + storeDBPassphraseGroupDefault.set(useKeychain) + } if useKeychain { if kcDatabasePassword.set(newKey) { await resetFormAfterEncryption(true) @@ -148,6 +159,9 @@ struct DatabaseEncryptionView: View { await operationEnded(.error(title: "Keychain error", error: "Error saving passphrase to keychain")) } } else { + if migration { + removePassphraseFromKeyChain() + } await resetFormAfterEncryption() await operationEnded(.databaseEncrypted) } @@ -174,7 +188,10 @@ struct DatabaseEncryptionView: View { private func setUseKeychain(_ value: Bool) { useKeychain = value - storeDBPassphraseGroupDefault.set(value) + // Postpone it when migrating to the end of encryption process + if !migration { + storeDBPassphraseGroupDefault.set(value) + } } private func databaseEncryptionAlert(_ alertItem: DatabaseEncryptionAlert) -> Alert { @@ -184,13 +201,7 @@ struct DatabaseEncryptionView: View { title: Text("Remove passphrase from keychain?"), message: Text("Instant push notifications will be hidden!\n") + storeSecurelyDanger(), primaryButton: .destructive(Text("Remove")) { - if kcDatabasePassword.remove() { - logger.debug("passphrase removed from keychain") - setUseKeychain(false) - storedKey = false - } else { - alert = .error(title: "Keychain error", error: "Failed to remove passphrase") - } + removePassphraseFromKeyChain() }, secondaryButton: .cancel() { withAnimation { useKeychainToggle = true } @@ -236,6 +247,16 @@ struct DatabaseEncryptionView: View { } } + private func removePassphraseFromKeyChain() { + if kcDatabasePassword.remove() { + logger.debug("passphrase removed from keychain") + setUseKeychain(false) + storedKey = false + } else { + alert = .error(title: "Keychain error", error: "Failed to remove passphrase") + } + } + private func storeSecurelySaved() -> Text { Text("Please store passphrase securely, you will NOT be able to change it if you lose it.") } @@ -346,6 +367,6 @@ func validKey(_ s: String) -> Bool { struct DatabaseEncryptionView_Previews: PreviewProvider { static var previews: some View { - DatabaseEncryptionView(useKeychain: Binding.constant(true)) + DatabaseEncryptionView(useKeychain: Binding.constant(true), migration: false) } } diff --git a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift index 52ded44782..f8d282a6d1 100644 --- a/apps/ios/Shared/Views/Database/DatabaseErrorView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseErrorView.swift @@ -64,7 +64,7 @@ struct DatabaseErrorView: View { case let .migrationError(mtrError): titleText("Incompatible database version") fileNameText(dbFile) - Text("Error: ") + Text(mtrErrorDescription(mtrError)) + Text("Error: ") + Text(DatabaseErrorView.mtrErrorDescription(mtrError)) } case let .errorSQL(dbFile, migrationSQLError): titleText("Database error") @@ -105,7 +105,7 @@ struct DatabaseErrorView: View { Text("Migrations: \(ms.joined(separator: ", "))") } - private func mtrErrorDescription(_ err: MTRError) -> LocalizedStringKey { + static func mtrErrorDescription(_ err: MTRError) -> LocalizedStringKey { switch err { case let .noDown(dbMigrations): return "database version is newer than the app, but no down migration for: \(dbMigrations.joined(separator: ", "))" diff --git a/apps/ios/Shared/Views/Database/DatabaseView.swift b/apps/ios/Shared/Views/Database/DatabaseView.swift index 31b1f618e3..2e0cd7738f 100644 --- a/apps/ios/Shared/Views/Database/DatabaseView.swift +++ b/apps/ios/Shared/Views/Database/DatabaseView.swift @@ -116,7 +116,7 @@ struct DatabaseView: View { let color: Color = unencrypted ? .orange : .secondary settingsRow(unencrypted ? "lock.open" : useKeychain ? "key" : "lock", color: color) { NavigationLink { - DatabaseEncryptionView(useKeychain: $useKeychain) + DatabaseEncryptionView(useKeychain: $useKeychain, migration: false) .navigationTitle("Database passphrase") } label: { Text("Database passphrase") @@ -485,6 +485,10 @@ func deleteChatAsync() async throws { _ = kcDatabasePassword.remove() storeDBPassphraseGroupDefault.set(true) deleteAppDatabaseAndFiles() + // Clean state so when creating new user the app will start chat automatically (see CreateProfile:createProfile()) + DispatchQueue.main.async { + ChatModel.shared.users = [] + } } struct DatabaseView_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift b/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift index 046929a9d0..ae6af24f53 100644 --- a/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift +++ b/apps/ios/Shared/Views/Database/MigrateToAppGroupView.swift @@ -188,6 +188,7 @@ struct MigrateToAppGroupView: View { let config = ArchiveConfig(archivePath: getDocumentsDirectory().appendingPathComponent(archiveName).path) Task { do { + try apiSaveAppSettings(settings: AppSettings.current.prepareForExport()) try await apiExportArchive(config: config) await MainActor.run { setV3DBMigration(.exported) } } catch let error { @@ -204,7 +205,11 @@ struct MigrateToAppGroupView: View { resetChatCtrl() try await MainActor.run { try initializeChat(start: false) } let _ = try await apiImportArchive(config: config) - await MainActor.run { setV3DBMigration(.migrated) } + let appSettings = try apiGetAppSettings(settings: AppSettings.current.prepareForExport()) + await MainActor.run { + appSettings.importIntoApp() + setV3DBMigration(.migrated) + } } catch let error { dbContainerGroupDefault.set(.documents) await MainActor.run { @@ -216,16 +221,22 @@ struct MigrateToAppGroupView: View { } } -func exportChatArchive() async throws -> URL { +func exportChatArchive(_ storagePath: URL? = nil) async throws -> URL { let archiveTime = Date.now let ts = archiveTime.ISO8601Format(Date.ISO8601FormatStyle(timeSeparator: .omitted)) let archiveName = "simplex-chat.\(ts).zip" - let archivePath = getDocumentsDirectory().appendingPathComponent(archiveName) + let archivePath = (storagePath ?? getDocumentsDirectory()).appendingPathComponent(archiveName) let config = ArchiveConfig(archivePath: archivePath.path) + // Settings should be saved before changing a passphrase, otherwise the database needs to be migrated first + if !ChatModel.shared.chatDbChanged { + try apiSaveAppSettings(settings: AppSettings.current.prepareForExport()) + } try await apiExportArchive(config: config) - deleteOldArchive() - UserDefaults.standard.set(archiveName, forKey: DEFAULT_CHAT_ARCHIVE_NAME) - chatArchiveTimeDefault.set(archiveTime) + if storagePath == nil { + deleteOldArchive() + UserDefaults.standard.set(archiveName, forKey: DEFAULT_CHAT_ARCHIVE_NAME) + chatArchiveTimeDefault.set(archiveTime) + } return archivePath } diff --git a/apps/ios/Shared/Views/Helpers/ContextMenu.swift b/apps/ios/Shared/Views/Helpers/ContextMenu.swift index 287aba5262..3b82d6eb95 100644 --- a/apps/ios/Shared/Views/Helpers/ContextMenu.swift +++ b/apps/ios/Shared/Views/Helpers/ContextMenu.swift @@ -11,26 +11,31 @@ import UIKit import SwiftUI extension View { - func uiKitContextMenu(menu: Binding, allowMenu: Binding) -> some View { - self.overlay { - if allowMenu.wrappedValue { - self.overlay(Color(uiColor: .systemBackground)).overlay(InteractionView(content: self, menu: menu)) - } + func uiKitContextMenu(maxWidth: CGFloat, menu: Binding, allowMenu: Binding) -> some View { + Group { + if allowMenu.wrappedValue { + InteractionView(content: self, maxWidth: maxWidth, menu: menu) + .fixedSize(horizontal: true, vertical: false) + } else { + self } + } } } -private struct InteractionConfig { - let content: Content - let menu: UIMenu +private class HostingViewHolder: UIView { + var contentSize: CGSize = CGSizeMake(0, 0) + override var intrinsicContentSize: CGSize { get { contentSize } } } -private struct InteractionView: UIViewRepresentable { +struct InteractionView: UIViewRepresentable { let content: Content + var maxWidth: CGFloat @Binding var menu: UIMenu func makeUIView(context: Context) -> UIView { - let view = UIView() + let view = HostingViewHolder() + view.contentSize = CGSizeMake(maxWidth, .infinity) view.backgroundColor = .clear let hostView = UIHostingController(rootView: content) hostView.view.translatesAutoresizingMaskIntoConstraints = false @@ -44,12 +49,16 @@ private struct InteractionView: UIViewRepresentable { ] view.addSubview(hostView.view) view.addConstraints(constraints) + view.layer.cornerRadius = 18 + hostView.view.layer.cornerRadius = 18 let menuInteraction = UIContextMenuInteraction(delegate: context.coordinator) view.addInteraction(menuInteraction) return view } - func updateUIView(_ uiView: UIView, context: Context) {} + func updateUIView(_ uiView: UIView, context: Context) { + (uiView as! HostingViewHolder).contentSize = uiView.subviews[0].sizeThatFits(CGSizeMake(maxWidth, .infinity)) + } func makeCoordinator() -> Coordinator { Coordinator(self) diff --git a/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift new file mode 100644 index 0000000000..645de4c3f8 --- /dev/null +++ b/apps/ios/Shared/Views/Migration/MigrateFromDevice.swift @@ -0,0 +1,734 @@ +// +// MigrateFromDevice.swift +// SimpleX (iOS) +// +// Created by Avently on 14.02.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +private enum MigrationFromState: Equatable { + case chatStopInProgress + case chatStopFailed(reason: String) + case passphraseNotSet + case passphraseConfirmation + case uploadConfirmation + case archiving + case uploadProgress(uploadedBytes: Int64, totalBytes: Int64, fileId: Int64, archivePath: URL, ctrl: chat_ctrl?) + case uploadFailed(totalBytes: Int64, archivePath: URL) + case linkCreation + case linkShown(fileId: Int64, link: String, archivePath: URL, ctrl: chat_ctrl) + case finished(chatDeletion: Bool) +} + +private enum MigrateFromDeviceViewAlert: Identifiable { + case deleteChat(_ title: LocalizedStringKey = "Delete chat profile?", _ text: LocalizedStringKey = "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost.") + case startChat(_ title: LocalizedStringKey = "Start chat?", _ text: LocalizedStringKey = "Warning: starting chat on multiple devices is not supported and will cause message delivery failures") + + case wrongPassphrase(title: LocalizedStringKey = "Wrong passphrase!", message: LocalizedStringKey = "Enter correct passphrase.") + case invalidConfirmation(title: LocalizedStringKey = "Invalid migration confirmation") + case keychainError(_ title: LocalizedStringKey = "Keychain error") + case databaseError(_ title: LocalizedStringKey = "Database error", message: String) + case unknownError(_ title: LocalizedStringKey = "Unknown error", message: String) + + case error(title: LocalizedStringKey, error: String = "") + + var id: String { + switch self { + case let .deleteChat(title, text): return "\(title) \(text)" + case let .startChat(title, text): return "\(title) \(text)" + + case .wrongPassphrase: return "wrongPassphrase" + case .invalidConfirmation: return "invalidConfirmation" + case .keychainError: return "keychainError" + case let .databaseError(title, message): return "\(title) \(message)" + case let .unknownError(title, message): return "\(title) \(message)" + + case let .error(title, _): return "error \(title)" + } + } +} + +struct MigrateFromDevice: View { + @EnvironmentObject var m: ChatModel + @Environment(\.dismiss) var dismiss: DismissAction + @Binding var showSettings: Bool + @Binding var showProgressOnSettings: Bool + @State private var migrationState: MigrationFromState = .chatStopInProgress + @State private var useKeychain = storeDBPassphraseGroupDefault.get() + @AppStorage(GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE, store: groupDefaults) private var initialRandomDBPassphrase: Bool = false + @State private var alert: MigrateFromDeviceViewAlert? + @State private var authorized = !UserDefaults.standard.bool(forKey: DEFAULT_PERFORM_LA) + private let tempDatabaseUrl = urlForTemporaryDatabase() + @State private var chatReceiver: MigrationChatReceiver? = nil + @State private var backDisabled: Bool = false + + var body: some View { + if authorized { + migrateView() + } else { + Button(action: runAuth) { Label("Unlock", systemImage: "lock") } + .onAppear(perform: runAuth) + } + } + + private func runAuth() { authorize(NSLocalizedString("Open migration to another device", comment: "authentication reason"), $authorized) } + + func migrateView() -> some View { + VStack { + switch migrationState { + case .chatStopInProgress: + chatStopInProgressView() + case let .chatStopFailed(reason): + chatStopFailedView(reason) + case .passphraseNotSet: + passphraseNotSetView() + case .passphraseConfirmation: + PassphraseConfirmationView(migrationState: $migrationState, alert: $alert) + case .uploadConfirmation: + uploadConfirmationView() + case .archiving: + archivingView() + case let .uploadProgress(uploaded, total, _, archivePath, _): + uploadProgressView(uploaded, totalBytes: total, archivePath) + case let .uploadFailed(total, archivePath): + uploadFailedView(totalBytes: total, archivePath) + case .linkCreation: + linkCreationView() + case let .linkShown(fileId, link, archivePath, ctrl): + linkShownView(fileId, link, archivePath, ctrl) + case let .finished(chatDeletion): + finishedView(chatDeletion) + } + } + .modifier(BackButton(label: "Back", disabled: $backDisabled) { + dismiss() + }) + .onChange(of: migrationState) { state in + backDisabled = switch migrationState { + case .chatStopInProgress, .archiving, .linkShown, .finished: true + case .chatStopFailed, .passphraseNotSet, .passphraseConfirmation, .uploadConfirmation, .uploadProgress, .uploadFailed, .linkCreation: false + } + } + .onAppear { + stopChat() + } + .onDisappear { + Task { + if !backDisabled { + await MainActor.run { + showProgressOnSettings = true + } + await startChatAndDismiss(false) + await MainActor.run { + showProgressOnSettings = false + } + } + if case let .uploadProgress(_, _, fileId, _, ctrl) = migrationState, let ctrl { + await cancelUploadedArchive(fileId, ctrl) + } + chatReceiver?.stopAndCleanUp() + try? FileManager.default.removeItem(at: getMigrationTempFilesDirectory()) + } + } + .alert(item: $alert) { alert in + switch alert { + case let .startChat(title, text): + return Alert( + title: Text(title), + message: Text(text), + primaryButton: .destructive(Text("Start chat")) { + Task { + await startChatAndDismiss() + } + }, + secondaryButton: .cancel() + ) + case let .deleteChat(title, text): + return Alert( + title: Text(title), + message: Text(text), + primaryButton: .default(Text("Delete")) { + deleteChatAndDismiss() + }, + secondaryButton: .cancel() + ) + case let .wrongPassphrase(title, message): + return Alert(title: Text(title), message: Text(message)) + case let .invalidConfirmation(title): + return Alert(title: Text(title)) + case let .keychainError(title): + return Alert(title: Text(title)) + case let .databaseError(title, message): + return Alert(title: Text(title), message: Text(message)) + case let .unknownError(title, message): + return Alert(title: Text(title), message: Text(message)) + case let .error(title, error): + return Alert(title: Text(title), message: Text(error)) + } + } + .interactiveDismissDisabled(backDisabled) + } + + private func chatStopInProgressView() -> some View { + ZStack { + List { + Section {} header: { + Text("Stopping chat") + } + } + progressView() + } + } + + private func chatStopFailedView(_ reason: String) -> some View { + List { + Section { + Text(reason) + Button(action: stopChat) { + settingsRow("stop.fill") { + Text("Stop chat").foregroundColor(.red) + } + } + } header: { + Text("Error stopping chat") + } footer: { + Text("In order to continue, chat should be stopped.") + .font(.callout) + } + } + } + + private func passphraseNotSetView() -> some View { + DatabaseEncryptionView(useKeychain: $useKeychain, migration: true) + .onChange(of: initialRandomDBPassphrase) { initial in + if !initial { + migrationState = .uploadConfirmation + } + } + } + + private func uploadConfirmationView() -> some View { + List { + Section { + Button(action: { migrationState = .archiving }) { + settingsRow("tray.and.arrow.up") { + Text("Archive and upload").foregroundColor(.accentColor) + } + } + } header: { + Text("Confirm upload") + } footer: { + Text("All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays.") + .font(.callout) + } + } + } + + private func archivingView() -> some View { + ZStack { + List { + Section {} header: { + Text("Archiving database") + } + } + progressView() + } + .onAppear { + exportArchive() + } + } + + private func uploadProgressView(_ uploadedBytes: Int64, totalBytes: Int64, _ archivePath: URL) -> some View { + ZStack { + List { + Section {} header: { + Text("Uploading archive") + } + } + let ratio = Float(uploadedBytes) / Float(totalBytes) + MigrateFromDevice.largeProgressView(ratio, "\(Int(ratio * 100))%", "\(ByteCountFormatter.string(fromByteCount: uploadedBytes, countStyle: .binary)) uploaded") + } + .onAppear { + startUploading(totalBytes, archivePath) + } + } + + private func uploadFailedView(totalBytes: Int64, _ archivePath: URL) -> some View { + List { + Section { + Button(action: { + migrationState = .uploadProgress(uploadedBytes: 0, totalBytes: totalBytes, fileId: 0, archivePath: archivePath, ctrl: nil) + }) { + settingsRow("tray.and.arrow.up") { + Text("Repeat upload").foregroundColor(.accentColor) + } + } + } header: { + Text("Upload failed") + } footer: { + Text("You can give another try.") + .font(.callout) + } + } + .onAppear { + chatReceiver?.stopAndCleanUp() + } + } + + private func linkCreationView() -> some View { + ZStack { + List { + Section {} header: { + Text("Creating archive link") + } + } + progressView() + } + } + + private func linkShownView(_ fileId: Int64, _ link: String, _ archivePath: URL, _ ctrl: chat_ctrl) -> some View { + List { + Section { + Button(action: { cancelMigration(fileId, ctrl) }) { + settingsRow("multiply") { + Text("Cancel migration").foregroundColor(.red) + } + } + Button(action: { finishMigration(fileId, ctrl) }) { + settingsRow("checkmark") { + Text("Finalize migration").foregroundColor(.accentColor) + } + } + } footer: { + VStack(alignment: .leading, spacing: 16) { + Text("**Warning**: the archive will be removed.") + Text("Choose _Migrate from another device_ on the new device and scan QR code.") + } + .font(.callout) + } + Section("Show QR code") { + SimpleXLinkQRCode(uri: link) + .padding() + .background( + RoundedRectangle(cornerRadius: 12, style: .continuous) + .fill(Color(uiColor: .secondarySystemGroupedBackground)) + ) + .padding(.horizontal) + .listRowBackground(Color.clear) + .listRowSeparator(.hidden) + .listRowInsets(EdgeInsets(top: 0, leading: 0, bottom: 0, trailing: 0)) + } + + Section("Or securely share this file link") { + shareLinkView(link) + } + .listRowInsets(EdgeInsets(top: 0, leading: 20, bottom: 0, trailing: 10)) + } + } + + private func finishedView(_ chatDeletion: Bool) -> some View { + ZStack { + List { + Section { + Button(action: { alert = .startChat() }) { + settingsRow("play.fill") { + Text("Start chat").foregroundColor(.red) + } + } + Button(action: { alert = .deleteChat() }) { + settingsRow("trash.fill") { + Text("Delete database from this device").foregroundColor(.accentColor) + } + } + } header: { + Text("Migration complete") + } footer: { + VStack(alignment: .leading, spacing: 16) { + Text("You **must not** use the same database on two devices.") + Text("**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection.") + } + .font(.callout) + } + } + if chatDeletion { + progressView() + } + } + } + + private func shareLinkView(_ link: String) -> some View { + HStack { + linkTextView(link) + Button { + showShareSheet(items: [link]) + } label: { + Image(systemName: "square.and.arrow.up") + .padding(.top, -7) + } + } + .frame(maxWidth: .infinity) + } + + private func linkTextView(_ link: String) -> some View { + Text(link) + .lineLimit(1) + .font(.caption) + .truncationMode(.middle) + } + + static func largeProgressView(_ value: Float, _ title: String, _ description: LocalizedStringKey) -> some View { + ZStack { + VStack { + Text(description) + .font(.title3) + .hidden() + + Text(title) + .font(.system(size: 54)) + .bold() + .foregroundColor(.accentColor) + + Text(description) + .font(.title3) + } + + Circle() + .trim(from: 0, to: CGFloat(value)) + .stroke( + Color.accentColor, + style: StrokeStyle(lineWidth: 27) + ) + .rotationEffect(.degrees(180)) + .animation(.linear, value: value) + .frame(maxWidth: .infinity) + .padding(.horizontal) + .padding(.horizontal) + } + .frame(maxWidth: .infinity) + } + + private func stopChat() { + Task { + do { + try await stopChatAsync() + do { + try apiSaveAppSettings(settings: AppSettings.current.prepareForExport()) + await MainActor.run { + migrationState = initialRandomDBPassphraseGroupDefault.get() ? .passphraseNotSet : .passphraseConfirmation + } + } catch let error { + alert = .error(title: "Error saving settings", error: error.localizedDescription) + migrationState = .chatStopFailed(reason: NSLocalizedString("Error saving settings", comment: "when migrating")) + } + } catch let e { + await MainActor.run { + migrationState = .chatStopFailed(reason: e.localizedDescription) + } + } + } + } + + private func exportArchive() { + Task { + do { + try? FileManager.default.createDirectory(at: getMigrationTempFilesDirectory(), withIntermediateDirectories: true) + let archivePath = try await exportChatArchive(getMigrationTempFilesDirectory()) + if let attrs = try? FileManager.default.attributesOfItem(atPath: archivePath.path), + let totalBytes = attrs[.size] as? Int64 { + await MainActor.run { + migrationState = .uploadProgress(uploadedBytes: 0, totalBytes: totalBytes, fileId: 0, archivePath: archivePath, ctrl: nil) + } + } else { + await MainActor.run { + alert = .error(title: "Exported file doesn't exist") + migrationState = .uploadConfirmation + } + } + } catch let error { + await MainActor.run { + alert = .error(title: "Error exporting chat database", error: responseError(error)) + migrationState = .uploadConfirmation + } + } + } + } + + private func initTemporaryDatabase() -> (chat_ctrl, User)? { + let (status, ctrl) = chatInitTemporaryDatabase(url: tempDatabaseUrl) + showErrorOnMigrationIfNeeded(status, $alert) + do { + if let ctrl, let user = try startChatWithTemporaryDatabase(ctrl: ctrl) { + return (ctrl, user) + } + } catch let error { + logger.error("Error while starting chat in temporary database: \(error.localizedDescription)") + } + return nil + } + + private func startUploading(_ totalBytes: Int64, _ archivePath: URL) { + Task { + guard let ctrlAndUser = initTemporaryDatabase() else { + return migrationState = .uploadFailed(totalBytes: totalBytes, archivePath: archivePath) + } + let (ctrl, user) = ctrlAndUser + chatReceiver = MigrationChatReceiver(ctrl: ctrl, databaseUrl: tempDatabaseUrl) { msg in + await MainActor.run { + switch msg { + case let .sndFileProgressXFTP(_, _, fileTransferMeta, sentSize, totalSize): + if case let .uploadProgress(uploaded, total, _, _, _) = migrationState, uploaded != total { + migrationState = .uploadProgress(uploadedBytes: sentSize, totalBytes: totalSize, fileId: fileTransferMeta.fileId, archivePath: archivePath, ctrl: ctrl) + } + case .sndFileRedirectStartXFTP: + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + migrationState = .linkCreation + } + case let .sndStandaloneFileComplete(_, fileTransferMeta, rcvURIs): + let cfg = getNetCfg() + let data = MigrationFileLinkData.init( + networkConfig: MigrationFileLinkData.NetworkConfig( + socksProxy: cfg.socksProxy, + hostMode: cfg.hostMode, + requiredHostMode: cfg.requiredHostMode + ) + ) + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + migrationState = .linkShown(fileId: fileTransferMeta.fileId, link: data.addToLink(link: rcvURIs[0]), archivePath: archivePath, ctrl: ctrl) + } + case .sndFileError: + alert = .error(title: "Upload failed", error: "Check your internet connection and try again") + migrationState = .uploadFailed(totalBytes: totalBytes, archivePath: archivePath) + default: + logger.debug("unsupported event: \(msg.responseType)") + } + } + } + chatReceiver?.start() + + let (res, error) = await uploadStandaloneFile(user: user, file: CryptoFile.plain(archivePath.lastPathComponent), ctrl: ctrl) + await MainActor.run { + guard let res = res else { + migrationState = .uploadFailed(totalBytes: totalBytes, archivePath: archivePath) + return alert = .error(title: "Error uploading the archive", error: error ?? "") + } + migrationState = .uploadProgress(uploadedBytes: 0, totalBytes: res.fileSize, fileId: res.fileId, archivePath: archivePath, ctrl: ctrl) + } + } + } + + private func cancelUploadedArchive(_ fileId: Int64, _ ctrl: chat_ctrl) async { + _ = await apiCancelFile(fileId: fileId, ctrl: ctrl) + } + + private func cancelMigration(_ fileId: Int64, _ ctrl: chat_ctrl) { + Task { + await cancelUploadedArchive(fileId, ctrl) + await startChatAndDismiss() + } + } + + private func finishMigration(_ fileId: Int64, _ ctrl: chat_ctrl) { + Task { + await cancelUploadedArchive(fileId, ctrl) + await MainActor.run { + migrationState = .finished(chatDeletion: false) + } + } + } + + private func deleteChatAndDismiss() { + Task { + do { + try await deleteChatAsync() + m.chatDbChanged = true + m.chatInitialized = false + migrationState = .finished(chatDeletion: true) + DispatchQueue.main.asyncAfter(deadline: .now()) { + resetChatCtrl() + do { + try initializeChat(start: false) + m.chatDbChanged = false + AppChatState.shared.set(.active) + } catch let error { + fatalError("Error starting chat \(responseError(error))") + } + showSettings = false + } + } catch let error { + alert = .error(title: "Error deleting database", error: responseError(error)) + } + } + } + + private func startChatAndDismiss(_ dismiss: Bool = true) async { + AppChatState.shared.set(.active) + do { + if m.chatDbChanged { + resetChatCtrl() + try initializeChat(start: true) + m.chatDbChanged = false + } else { + try startChat(refreshInvitations: true) + } + } catch let error { + alert = .error(title: "Error starting chat", error: responseError(error)) + } + // Hide settings anyway if chatDbStatus is not ok, probably passphrase needs to be entered + if dismiss || m.chatDbStatus != .ok { + await MainActor.run { + showSettings = false + } + } + } + + private static func urlForTemporaryDatabase() -> URL { + URL(fileURLWithPath: generateNewFileName(getMigrationTempFilesDirectory().path + "/" + "migration", "db", fullPath: true)) + } +} + +private struct PassphraseConfirmationView: View { + @Binding var migrationState: MigrationFromState + @State private var useKeychain = storeDBPassphraseGroupDefault.get() + @State private var currentKey: String = "" + @State private var verifyingPassphrase: Bool = false + @FocusState private var keyboardVisible: Bool + @Binding var alert: MigrateFromDeviceViewAlert? + + var body: some View { + ZStack { + List { + chatStoppedView() + Section { + PassphraseField(key: $currentKey, placeholder: "Current passphrase…", valid: validKey(currentKey)) + .focused($keyboardVisible) + Button(action: { + verifyingPassphrase = true + hideKeyboard() + Task { + await verifyDatabasePassphrase(currentKey) + verifyingPassphrase = false + } + }) { + settingsRow(useKeychain ? "key" : "lock", color: .secondary) { + Text("Verify passphrase") + } + } + .disabled(verifyingPassphrase || currentKey.isEmpty) + } header: { + Text("Verify database passphrase") + } footer: { + Text("Confirm that you remember database passphrase to migrate it.") + .font(.callout) + } + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + keyboardVisible = true + } + } + } + if verifyingPassphrase { + progressView() + } + } + } + + private func verifyDatabasePassphrase(_ dbKey: String) async { + do { + try await testStorageEncryption(key: dbKey) + await MainActor.run { + migrationState = .uploadConfirmation + } + } catch let error { + if case .chatCmdError(_, .errorDatabase(.errorOpen(.errorNotADatabase))) = error as? ChatResponse { + showErrorOnMigrationIfNeeded(.errorNotADatabase(dbFile: ""), $alert) + } else { + alert = .error(title: "Error", error: NSLocalizedString("Error verifying passphrase:", comment: "") + " " + String(String(describing: error))) + } + } + } +} + +private func showErrorOnMigrationIfNeeded(_ status: DBMigrationResult, _ alert: Binding) { + switch status { + case .invalidConfirmation: + alert.wrappedValue = .invalidConfirmation() + case .errorNotADatabase: + alert.wrappedValue = .wrongPassphrase() + case .errorKeychain: + alert.wrappedValue = .keychainError() + case let .errorSQL(_, error): + alert.wrappedValue = .databaseError(message: error) + case let .unknown(error): + alert.wrappedValue = .unknownError(message: error) + case .errorMigration: () + case .ok: () + } +} + +private func progressView() -> some View { + VStack { + ProgressView().scaleEffect(2) + } + .frame(maxWidth: .infinity, maxHeight: .infinity ) +} + +func chatStoppedView() -> some View { + settingsRow("exclamationmark.octagon.fill", color: .red) { + Text("Chat is stopped") + } +} + +private class MigrationChatReceiver { + let ctrl: chat_ctrl + let databaseUrl: URL + let processReceivedMsg: (ChatResponse) async -> Void + private var receiveLoop: Task? + private var receiveMessages = true + + init(ctrl: chat_ctrl, databaseUrl: URL, _ processReceivedMsg: @escaping (ChatResponse) async -> Void) { + self.ctrl = ctrl + self.databaseUrl = databaseUrl + self.processReceivedMsg = processReceivedMsg + } + + func start() { + logger.debug("MigrationChatReceiver.start") + receiveMessages = true + if receiveLoop != nil { return } + receiveLoop = Task { await receiveMsgLoop() } + } + + func receiveMsgLoop() async { + // TODO use function that has timeout + if let msg = await chatRecvMsg(ctrl) { + Task { + await TerminalItems.shared.add(.resp(.now, msg)) + } + logger.debug("processReceivedMsg: \(msg.responseType)") + await processReceivedMsg(msg) + } + if self.receiveMessages { + _ = try? await Task.sleep(nanoseconds: 7_500_000) + await receiveMsgLoop() + } + } + + func stopAndCleanUp() { + logger.debug("MigrationChatReceiver.stop") + receiveMessages = false + receiveLoop?.cancel() + receiveLoop = nil + chat_close_store(ctrl) + try? FileManager.default.removeItem(atPath: "\(databaseUrl.path)_chat.db") + try? FileManager.default.removeItem(atPath: "\(databaseUrl.path)_agent.db") + } +} + +struct MigrateFromDevice_Previews: PreviewProvider { + static var previews: some View { + MigrateFromDevice(showSettings: Binding.constant(true), showProgressOnSettings: Binding.constant(false)) + } +} diff --git a/apps/ios/Shared/Views/Migration/MigrateToDevice.swift b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift new file mode 100644 index 0000000000..9afd0dd406 --- /dev/null +++ b/apps/ios/Shared/Views/Migration/MigrateToDevice.swift @@ -0,0 +1,714 @@ +// +// MigrateToDevice.swift +// SimpleX (iOS) +// +// Created by Avently on 23.02.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import SwiftUI +import SimpleXChat + +enum MigrationToDeviceState: Codable, Equatable { + case downloadProgress(link: String, archiveName: String) + case archiveImport(archiveName: String) + case passphrase + + // Here we check whether it's needed to show migration process after app restart or not + // It's important to NOT show the process when archive was corrupted/not fully downloaded + static func makeMigrationState() -> MigrationToState? { + let state: MigrationToDeviceState? = UserDefaults.standard.string(forKey: DEFAULT_MIGRATION_TO_STAGE) != nil ? decodeJSON(UserDefaults.standard.string(forKey: DEFAULT_MIGRATION_TO_STAGE)!) : nil + var initial: MigrationToState? = .pasteOrScanLink + //logger.debug("Inited with migrationState: \(String(describing: state))") + switch state { + case nil: + initial = nil + case .downloadProgress: + // No migration happens at the moment actually since archive were not downloaded fully + logger.debug("MigrateToDevice: archive wasn't fully downloaded, removed broken file") + initial = nil + case let .archiveImport(archiveName): + let archivePath = getMigrationTempFilesDirectory().path + "/" + archiveName + initial = .archiveImportFailed(archivePath: archivePath) + case .passphrase: + initial = .passphrase(passphrase: "") + } + if initial == nil { + UserDefaults.standard.removeObject(forKey: DEFAULT_MIGRATION_TO_STAGE) + try? FileManager.default.removeItem(at: getMigrationTempFilesDirectory()) + } + return initial + } + + static func save(_ state: MigrationToDeviceState?) { + if let state { + UserDefaults.standard.setValue(encodeJSON(state), forKey: DEFAULT_MIGRATION_TO_STAGE) + } else { + UserDefaults.standard.removeObject(forKey: DEFAULT_MIGRATION_TO_STAGE) + } + } +} + +enum MigrationToState: Equatable { + case pasteOrScanLink + case linkDownloading(link: String) + case downloadProgress(downloadedBytes: Int64, totalBytes: Int64, fileId: Int64, link: String, archivePath: String, ctrl: chat_ctrl?) + case downloadFailed(totalBytes: Int64, link: String, archivePath: String) + case archiveImport(archivePath: String) + case archiveImportFailed(archivePath: String) + case passphrase(passphrase: String) + case migrationConfirmation(status: DBMigrationResult, passphrase: String, useKeychain: Bool) + case migration(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Bool) + case onion(appSettings: AppSettings) +} + +private enum MigrateToDeviceViewAlert: Identifiable { + case chatImportedWithErrors(title: LocalizedStringKey = "Chat database imported", + text: LocalizedStringKey = "Some non-fatal errors occurred during import - you may see Chat console for more details.") + + case wrongPassphrase(title: LocalizedStringKey = "Wrong passphrase!", message: LocalizedStringKey = "Enter correct passphrase.") + case invalidConfirmation(title: LocalizedStringKey = "Invalid migration confirmation") + case keychainError(_ title: LocalizedStringKey = "Keychain error") + case databaseError(_ title: LocalizedStringKey = "Database error", message: String) + case unknownError(_ title: LocalizedStringKey = "Unknown error", message: String) + + case error(title: LocalizedStringKey, error: String = "") + + var id: String { + switch self { + case .chatImportedWithErrors: return "chatImportedWithErrors" + + case .wrongPassphrase: return "wrongPassphrase" + case .invalidConfirmation: return "invalidConfirmation" + case .keychainError: return "keychainError" + case let .databaseError(title, message): return "\(title) \(message)" + case let .unknownError(title, message): return "\(title) \(message)" + + case let .error(title, _): return "error \(title)" + } + } +} + +struct MigrateToDevice: View { + @EnvironmentObject var m: ChatModel + @Environment(\.dismiss) var dismiss: DismissAction + @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false + @Binding var migrationState: MigrationToState? + @State private var useKeychain = storeDBPassphraseGroupDefault.get() + @State private var alert: MigrateToDeviceViewAlert? + private let tempDatabaseUrl = urlForTemporaryDatabase() + @State private var chatReceiver: MigrationChatReceiver? = nil + // Prevent from hiding the view until migration is finished or app deleted + @State private var backDisabled: Bool = false + @State private var showQRCodeScanner: Bool = true + + var body: some View { + VStack { + switch migrationState { + case nil: EmptyView() + case .pasteOrScanLink: + pasteOrScanLinkView() + case let .linkDownloading(link): + linkDownloadingView(link) + case let .downloadProgress(downloaded, total, _, _, _, _): + downloadProgressView(downloaded, totalBytes: total) + case let .downloadFailed(total, link, archivePath): + downloadFailedView(totalBytes: total, link, archivePath) + case let .archiveImport(archivePath): + archiveImportView(archivePath) + case let .archiveImportFailed(archivePath): + archiveImportFailedView(archivePath) + case let .passphrase(passphrase): + PassphraseEnteringView(migrationState: $migrationState, currentKey: passphrase, alert: $alert) + case let .migrationConfirmation(status, passphrase, useKeychain): + migrationConfirmationView(status, passphrase, useKeychain) + case let .migration(passphrase, confirmation, useKeychain): + migrationView(passphrase, confirmation, useKeychain) + case let .onion(appSettings): + OnionView(appSettings: appSettings, finishMigration: finishMigration) + } + } + .onAppear { + backDisabled = switch migrationState { + case nil, .pasteOrScanLink, .linkDownloading, .downloadProgress, .downloadFailed, .archiveImportFailed: false + case .archiveImport, .passphrase, .migrationConfirmation, .migration, .onion: true + } + } + .onChange(of: migrationState) { state in + backDisabled = switch state { + case nil, .pasteOrScanLink, .linkDownloading, .downloadProgress, .downloadFailed, .archiveImportFailed: false + case .archiveImport, .passphrase, .migrationConfirmation, .migration, .onion: true + } + } + .onDisappear { + Task { + if case .archiveImportFailed = migrationState { + // Original database is not exist, nothing is setup correctly for showing to a user yet. Return to clean state + deleteAppDatabaseAndFiles() + initChatAndMigrate() + } else if case let .downloadProgress(_, _, fileId, _, _, ctrl) = migrationState, let ctrl { + await stopArchiveDownloading(fileId, ctrl) + } + chatReceiver?.stopAndCleanUp() + if !backDisabled { + try? FileManager.default.removeItem(at: getMigrationTempFilesDirectory()) + MigrationToDeviceState.save(nil) + } + } + } + .alert(item: $alert) { alert in + switch alert { + case let .chatImportedWithErrors(title, text): + return Alert(title: Text(title), message: Text(text)) + case let .wrongPassphrase(title, message): + return Alert(title: Text(title), message: Text(message)) + case let .invalidConfirmation(title): + return Alert(title: Text(title)) + case let .keychainError(title): + return Alert(title: Text(title)) + case let .databaseError(title, message): + return Alert(title: Text(title), message: Text(message)) + case let .unknownError(title, message): + return Alert(title: Text(title), message: Text(message)) + case let .error(title, error): + return Alert(title: Text(title), message: Text(error)) + } + } + .interactiveDismissDisabled(backDisabled) + } + + private func pasteOrScanLinkView() -> some View { + ZStack { + List { + Section("Scan QR code") { + ScannerInView(showQRCodeScanner: $showQRCodeScanner) { resp in + switch resp { + case let .success(r): + let link = r.string + if strHasSimplexFileLink(link.trimmingCharacters(in: .whitespaces)) { + migrationState = .linkDownloading(link: link.trimmingCharacters(in: .whitespaces)) + } else { + alert = .error(title: "Invalid link", error: "The text you pasted is not a SimpleX link.") + } + case let .failure(e): + logger.error("processQRCode QR code error: \(e.localizedDescription)") + alert = .error(title: "Invalid link", error: "The text you pasted is not a SimpleX link.") + } + } + } + if developerTools { + Section("Or paste archive link") { + pasteLinkView() + } + } + } + } + } + + private func pasteLinkView() -> some View { + Button { + if let str = UIPasteboard.general.string { + if strHasSimplexFileLink(str.trimmingCharacters(in: .whitespaces)) { + migrationState = .linkDownloading(link: str.trimmingCharacters(in: .whitespaces)) + } else { + alert = .error(title: "Invalid link", error: "The text you pasted is not a SimpleX link.") + } + } + } label: { + Text("Tap to paste link") + } + .disabled(!ChatModel.shared.pasteboardHasStrings) + .frame(maxWidth: .infinity, alignment: .center) + } + + private func linkDownloadingView(_ link: String) -> some View { + ZStack { + List { + Section {} header: { + Text("Downloading link details") + } + } + progressView() + } + .onAppear { + downloadLinkDetails(link) + } + } + + private func downloadProgressView(_ downloadedBytes: Int64, totalBytes: Int64) -> some View { + ZStack { + List { + Section {} header: { + Text("Downloading archive") + } + } + let ratio = Float(downloadedBytes) / Float(max(totalBytes, 1)) + MigrateFromDevice.largeProgressView(ratio, "\(Int(ratio * 100))%", "\(ByteCountFormatter.string(fromByteCount: downloadedBytes, countStyle: .binary)) downloaded") + } + } + + private func downloadFailedView(totalBytes: Int64, _ link: String, _ archivePath: String) -> some View { + List { + Section { + Button(action: { + try? FileManager.default.removeItem(atPath: archivePath) + migrationState = .linkDownloading(link: link) + }) { + settingsRow("tray.and.arrow.down") { + Text("Repeat download").foregroundColor(.accentColor) + } + } + } header: { + Text("Download failed") + } footer: { + Text("You can give another try.") + .font(.callout) + } + } + .onAppear { + chatReceiver?.stopAndCleanUp() + try? FileManager.default.removeItem(atPath: archivePath) + MigrationToDeviceState.save(nil) + } + } + + private func archiveImportView(_ archivePath: String) -> some View { + ZStack { + List { + Section {} header: { + Text("Importing archive") + } + } + progressView() + } + .onAppear { + importArchive(archivePath) + } + } + + private func archiveImportFailedView(_ archivePath: String) -> some View { + List { + Section { + Button(action: { + migrationState = .archiveImport(archivePath: archivePath) + }) { + settingsRow("square.and.arrow.down") { + Text("Repeat import").foregroundColor(.accentColor) + } + } + } header: { + Text("Import failed") + } footer: { + Text("You can give another try.") + .font(.callout) + } + } + } + + private func migrationConfirmationView(_ status: DBMigrationResult, _ passphrase: String, _ useKeychain: Bool) -> some View { + List { + let (header, button, footer, confirmation): (LocalizedStringKey, LocalizedStringKey?, String, MigrationConfirmation?) = switch status { + case let .errorMigration(_, migrationError): + switch migrationError { + case .upgrade: + ("Database upgrade", + "Upgrade and open chat", + "", + .yesUp) + case .downgrade: + ("Database downgrade", + "Downgrade and open chat", + NSLocalizedString("Warning: you may lose some data!", comment: ""), + .yesUpDown) + case let .migrationError(mtrError): + ("Incompatible database version", + nil, + "\(NSLocalizedString("Error: ", comment: "")) \(DatabaseErrorView.mtrErrorDescription(mtrError))", + nil) + } + default: ("Error", nil, "Unknown error", nil) + } + Section { + if let button, let confirmation { + Button(action: { + migrationState = .migration(passphrase: passphrase, confirmation: confirmation, useKeychain: useKeychain) + }) { + settingsRow("square.and.arrow.down") { + Text(button).foregroundColor(.accentColor) + } + } + } else { + EmptyView() + } + } header: { + Text(header) + } footer: { + Text(footer) + .font(.callout) + } + } + } + + private func migrationView(_ passphrase: String, _ confirmation: MigrationConfirmation, _ useKeychain: Bool) -> some View { + ZStack { + List { + Section {} header: { + Text("Migrating") + } + } + progressView() + } + .onAppear { + startChat(passphrase, confirmation, useKeychain) + } + } + + struct OnionView: View { + @State var appSettings: AppSettings + @State private var onionHosts: OnionHosts = .no + var finishMigration: (AppSettings) -> Void + + var body: some View { + List { + Section { + Button(action: { + var updated = appSettings.networkConfig! + let (hostMode, requiredHostMode) = onionHosts.hostMode + updated.hostMode = hostMode + updated.requiredHostMode = requiredHostMode + updated.socksProxy = nil + appSettings.networkConfig = updated + finishMigration(appSettings) + }) { + settingsRow("checkmark") { + Text("Apply").foregroundColor(.accentColor) + } + } + } header: { + Text("Confirm network settings") + } footer: { + Text("Please confirm that network settings are correct for this device.") + .font(.callout) + } + + Section("Network settings") { + Picker("Use .onion hosts", selection: $onionHosts) { + ForEach(OnionHosts.values, id: \.self) { Text($0.text) } + } + .frame(height: 36) + } + } + } + } + + private func downloadLinkDetails(_ link: String) { + let archiveTime = Date.now + let ts = archiveTime.ISO8601Format(Date.ISO8601FormatStyle(timeSeparator: .omitted)) + let archiveName = "simplex-chat.\(ts).zip" + let archivePath = getMigrationTempFilesDirectory().appendingPathComponent(archiveName) + + startDownloading(0, link, archivePath.path) + } + + private func initTemporaryDatabase() -> (chat_ctrl, User)? { + let (status, ctrl) = chatInitTemporaryDatabase(url: tempDatabaseUrl) + showErrorOnMigrationIfNeeded(status, $alert) + do { + if let ctrl, let user = try startChatWithTemporaryDatabase(ctrl: ctrl) { + return (ctrl, user) + } + } catch let error { + logger.error("Error while starting chat in temporary database: \(error.localizedDescription)") + } + return nil + } + + private func startDownloading(_ totalBytes: Int64, _ link: String, _ archivePath: String) { + Task { + guard let ctrlAndUser = initTemporaryDatabase() else { + return migrationState = .downloadFailed(totalBytes: totalBytes, link: link, archivePath: archivePath) + } + let (ctrl, user) = ctrlAndUser + chatReceiver = MigrationChatReceiver(ctrl: ctrl, databaseUrl: tempDatabaseUrl) { msg in + await MainActor.run { + switch msg { + case let .rcvFileProgressXFTP(_, _, receivedSize, totalSize, rcvFileTransfer): + migrationState = .downloadProgress(downloadedBytes: receivedSize, totalBytes: totalSize, fileId: rcvFileTransfer.fileId, link: link, archivePath: archivePath, ctrl: ctrl) + MigrationToDeviceState.save(.downloadProgress(link: link, archiveName: URL(fileURLWithPath: archivePath).lastPathComponent)) + case .rcvStandaloneFileComplete: + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + // User closed the whole screen before new state was saved + if migrationState == nil { + MigrationToDeviceState.save(nil) + } else { + migrationState = .archiveImport(archivePath: archivePath) + MigrationToDeviceState.save(.archiveImport(archiveName: URL(fileURLWithPath: archivePath).lastPathComponent)) + } + } + case .rcvFileError: + alert = .error(title: "Download failed", error: "File was deleted or link is invalid") + migrationState = .downloadFailed(totalBytes: totalBytes, link: link, archivePath: archivePath) + default: + logger.debug("unsupported event: \(msg.responseType)") + } + } + } + chatReceiver?.start() + + let (res, error) = await downloadStandaloneFile(user: user, url: link, file: CryptoFile.plain(URL(fileURLWithPath: archivePath).lastPathComponent), ctrl: ctrl) + if res == nil { + await MainActor.run { + migrationState = .downloadFailed(totalBytes: totalBytes, link: link, archivePath: archivePath) + } + return alert = .error(title: "Error downloading the archive", error: error ?? "") + } + } + } + + private func importArchive(_ archivePath: String) { + Task { + do { + if !hasChatCtrl() { + chatInitControllerRemovingDatabases() + } + try await apiDeleteStorage() + do { + let config = ArchiveConfig(archivePath: archivePath) + let archiveErrors = try await apiImportArchive(config: config) + if !archiveErrors.isEmpty { + alert = .chatImportedWithErrors() + } + await MainActor.run { + migrationState = .passphrase(passphrase: "") + MigrationToDeviceState.save(.passphrase) + } + } catch let error { + await MainActor.run { + migrationState = .archiveImportFailed(archivePath: archivePath) + } + alert = .error(title: "Error importing chat database", error: responseError(error)) + } + } catch let error { + await MainActor.run { + migrationState = .archiveImportFailed(archivePath: archivePath) + } + alert = .error(title: "Error deleting chat database", error: responseError(error)) + } + } + } + + + private func stopArchiveDownloading(_ fileId: Int64, _ ctrl: chat_ctrl) async { + _ = await apiCancelFile(fileId: fileId, ctrl: ctrl) + } + + private func startChat(_ passphrase: String, _ confirmation: MigrationConfirmation, _ useKeychain: Bool) { + if useKeychain { + _ = kcDatabasePassword.set(passphrase) + } else { + _ = kcDatabasePassword.remove() + } + storeDBPassphraseGroupDefault.set(useKeychain) + initialRandomDBPassphraseGroupDefault.set(false) + AppChatState.shared.set(.active) + Task { + do { + resetChatCtrl() + try initializeChat(start: false, confirmStart: false, dbKey: passphrase, refreshInvitations: true, confirmMigrations: confirmation) + var appSettings = try apiGetAppSettings(settings: AppSettings.current.prepareForExport()) + let hasOnionConfigured = appSettings.networkConfig?.socksProxy != nil || appSettings.networkConfig?.hostMode == .onionHost + appSettings.networkConfig?.socksProxy = nil + appSettings.networkConfig?.hostMode = .publicHost + appSettings.networkConfig?.requiredHostMode = true + await MainActor.run { + if hasOnionConfigured { + migrationState = .onion(appSettings: appSettings) + } else { + finishMigration(appSettings) + } + } + } catch let error { + hideView() + AlertManager.shared.showAlert(Alert(title: Text("Error starting chat"), message: Text(responseError(error)))) + } + } + } + + private func finishMigration(_ appSettings: AppSettings) { + do { + try? FileManager.default.removeItem(at: getMigrationTempFilesDirectory()) + MigrationToDeviceState.save(nil) + appSettings.importIntoApp() + try SimpleX.startChat(refreshInvitations: true) + AlertManager.shared.showAlertMsg(title: "Chat migrated!", message: "Finalize migration on another device.") + } catch let error { + AlertManager.shared.showAlert(Alert(title: Text("Error starting chat"), message: Text(responseError(error)))) + } + hideView() + } + + private func hideView() { + onboardingStageDefault.set(.onboardingComplete) + m.onboardingStage = .onboardingComplete + dismiss() + } + + private func strHasSimplexFileLink(_ text: String) -> Bool { + text.starts(with: "simplex:/file") || text.starts(with: "https://simplex.chat/file") + } + + private static func urlForTemporaryDatabase() -> URL { + URL(fileURLWithPath: generateNewFileName(getMigrationTempFilesDirectory().path + "/" + "migration", "db", fullPath: true)) + } +} + +private struct PassphraseEnteringView: View { + @Binding var migrationState: MigrationToState? + @State private var useKeychain = true + @State var currentKey: String + @State private var verifyingPassphrase: Bool = false + @FocusState private var keyboardVisible: Bool + @Binding var alert: MigrateToDeviceViewAlert? + + var body: some View { + ZStack { + List { + Section { + settingsRow("key", color: .secondary) { + Toggle("Save passphrase in Keychain", isOn: $useKeychain) + } + + PassphraseField(key: $currentKey, placeholder: "Current passphrase…", valid: validKey(currentKey)) + .focused($keyboardVisible) + Button(action: { + verifyingPassphrase = true + hideKeyboard() + Task { + let (status, _) = chatInitTemporaryDatabase(url: getAppDatabasePath(), key: currentKey, confirmation: .yesUp) + let success = switch status { + case .ok, .invalidConfirmation: true + default: false + } + if success { + await MainActor.run { + migrationState = .migration(passphrase: currentKey, confirmation: .yesUp, useKeychain: useKeychain) + } + } else if case .errorMigration = status { + await MainActor.run { + migrationState = .migrationConfirmation(status: status, passphrase: currentKey, useKeychain: useKeychain) + } + } else { + showErrorOnMigrationIfNeeded(status, $alert) + } + verifyingPassphrase = false + } + }) { + settingsRow("key", color: .secondary) { + Text("Open chat") + } + } + .disabled(verifyingPassphrase || currentKey.isEmpty) + } header: { + Text("Enter passphrase") + } footer: { + VStack(alignment: .leading, spacing: 16) { + if useKeychain { + Text("iOS Keychain is used to securely store passphrase - it allows receiving push notifications.") + } else { + Text("You have to enter passphrase every time the app starts - it is not stored on the device.") + Text("**Please note**: you will NOT be able to recover or change passphrase if you lose it.") + Text("**Warning**: Instant push notifications require passphrase saved in Keychain.") + } + } + .font(.callout) + .padding(.top, 1) + .onTapGesture { keyboardVisible = false } + } + .onAppear { + DispatchQueue.main.asyncAfter(deadline: .now() + 1) { + keyboardVisible = true + } + } + } + if verifyingPassphrase { + progressView() + } + } + } +} + +private func showErrorOnMigrationIfNeeded(_ status: DBMigrationResult, _ alert: Binding) { + switch status { + case .invalidConfirmation: + alert.wrappedValue = .invalidConfirmation() + case .errorNotADatabase: + alert.wrappedValue = .wrongPassphrase() + case .errorKeychain: + alert.wrappedValue = .keychainError() + case let .errorSQL(_, error): + alert.wrappedValue = .databaseError(message: error) + case let .unknown(error): + alert.wrappedValue = .unknownError(message: error) + case .errorMigration: () + case .ok: () + } +} + +private func progressView() -> some View { + VStack { + ProgressView().scaleEffect(2) + } + .frame(maxWidth: .infinity, maxHeight: .infinity ) +} + +private class MigrationChatReceiver { + let ctrl: chat_ctrl + let databaseUrl: URL + let processReceivedMsg: (ChatResponse) async -> Void + private var receiveLoop: Task? + private var receiveMessages = true + + init(ctrl: chat_ctrl, databaseUrl: URL, _ processReceivedMsg: @escaping (ChatResponse) async -> Void) { + self.ctrl = ctrl + self.databaseUrl = databaseUrl + self.processReceivedMsg = processReceivedMsg + } + + func start() { + logger.debug("MigrationChatReceiver.start") + receiveMessages = true + if receiveLoop != nil { return } + receiveLoop = Task { await receiveMsgLoop() } + } + + func receiveMsgLoop() async { + // TODO use function that has timeout + if let msg = await chatRecvMsg(ctrl) { + Task { + await TerminalItems.shared.add(.resp(.now, msg)) + } + logger.debug("processReceivedMsg: \(msg.responseType)") + await processReceivedMsg(msg) + } + if self.receiveMessages { + _ = try? await Task.sleep(nanoseconds: 7_500_000) + await receiveMsgLoop() + } + } + + func stopAndCleanUp() { + logger.debug("MigrationChatReceiver.stop") + receiveMessages = false + receiveLoop?.cancel() + receiveLoop = nil + chat_close_store(ctrl) + try? FileManager.default.removeItem(atPath: "\(databaseUrl.path)_chat.db") + try? FileManager.default.removeItem(atPath: "\(databaseUrl.path)_agent.db") + } +} + +struct MigrateToDevice_Previews: PreviewProvider { + static var previews: some View { + MigrateToDevice(migrationState: Binding.constant(.pasteOrScanLink)) + } +} diff --git a/apps/ios/Shared/Views/NewChat/NewChatView.swift b/apps/ios/Shared/Views/NewChat/NewChatView.swift index b78d92ffc8..7ece4fdee6 100644 --- a/apps/ios/Shared/Views/NewChat/NewChatView.swift +++ b/apps/ios/Shared/Views/NewChat/NewChatView.swift @@ -86,7 +86,7 @@ struct NewChatView: View { } } if case .connect = selection { - ConnectView(showQRCodeScanner: showQRCodeScanner, pastedLink: $pastedLink, alert: $alert) + ConnectView(showQRCodeScanner: $showQRCodeScanner, pastedLink: $pastedLink, alert: $alert) .transition(.move(edge: .trailing)) } } @@ -284,8 +284,7 @@ private struct InviteView: View { private struct ConnectView: View { @Environment(\.dismiss) var dismiss: DismissAction - @State var showQRCodeScanner = false - @State private var cameraAuthorizationStatus: AVAuthorizationStatus? + @Binding var showQRCodeScanner: Bool @Binding var pastedLink: String @Binding var alert: NewChatViewAlert? @State private var sheet: PlanAndConnectActionSheet? @@ -295,32 +294,13 @@ private struct ConnectView: View { Section("Paste the link you received") { pasteLinkView() } - - scanCodeView() + Section("Or scan QR code") { + ScannerInView(showQRCodeScanner: $showQRCodeScanner, processQRCode: processQRCode) + } } .actionSheet(item: $sheet) { s in planAndConnectActionSheet(s, dismiss: true, cleanup: { pastedLink = "" }) } - .onAppear { - let status = AVCaptureDevice.authorizationStatus(for: .video) - cameraAuthorizationStatus = status - if showQRCodeScanner { - switch status { - case .notDetermined: askCameraAuthorization() - case .restricted: showQRCodeScanner = false - case .denied: showQRCodeScanner = false - case .authorized: () - @unknown default: askCameraAuthorization() - } - } - } - } - - func askCameraAuthorization(_ cb: (() -> Void)? = nil) { - AVCaptureDevice.requestAccess(for: .video) { allowed in - cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) - if allowed { cb?() } - } } @ViewBuilder private func pasteLinkView() -> some View { @@ -351,8 +331,45 @@ private struct ConnectView: View { } } - private func scanCodeView() -> some View { - Section("Or scan QR code") { + private func processQRCode(_ resp: Result) { + switch resp { + case let .success(r): + let link = r.string + if strIsSimplexLink(r.string) { + connect(link) + } else { + alert = .newChatSomeAlert(alert: .someAlert( + alert: mkAlert(title: "Invalid QR code", message: "The code you scanned is not a SimpleX link QR code."), + id: "processQRCode: code is not a SimpleX link" + )) + } + case let .failure(e): + logger.error("processQRCode QR code error: \(e.localizedDescription)") + alert = .newChatSomeAlert(alert: .someAlert( + alert: mkAlert(title: "Invalid QR code", message: "Error scanning code: \(e.localizedDescription)"), + id: "processQRCode: failure" + )) + } + } + + private func connect(_ link: String) { + planAndConnect( + link, + showAlert: { alert = .planAndConnectAlert(alert: $0) }, + showActionSheet: { sheet = $0 }, + dismiss: true, + incognito: nil + ) + } +} + +struct ScannerInView: View { + @Binding var showQRCodeScanner: Bool + let processQRCode: (_ resp: Result) -> Void + @State private var cameraAuthorizationStatus: AVAuthorizationStatus? + + var body: some View { + Group { if showQRCodeScanner, case .authorized = cameraAuthorizationStatus { CodeScannerView(codeTypes: [.qr], scanMode: .continuous, completion: processQRCode) .aspectRatio(1, contentMode: .fit) @@ -396,37 +413,26 @@ private struct ConnectView: View { .disabled(cameraAuthorizationStatus == .restricted) } } - } - - private func processQRCode(_ resp: Result) { - switch resp { - case let .success(r): - let link = r.string - if strIsSimplexLink(r.string) { - connect(link) - } else { - alert = .newChatSomeAlert(alert: .someAlert( - alert: mkAlert(title: "Invalid QR code", message: "The code you scanned is not a SimpleX link QR code."), - id: "processQRCode: code is not a SimpleX link" - )) + .onAppear { + let status = AVCaptureDevice.authorizationStatus(for: .video) + cameraAuthorizationStatus = status + if showQRCodeScanner { + switch status { + case .notDetermined: askCameraAuthorization() + case .restricted: showQRCodeScanner = false + case .denied: showQRCodeScanner = false + case .authorized: () + @unknown default: askCameraAuthorization() + } } - case let .failure(e): - logger.error("processQRCode QR code error: \(e.localizedDescription)") - alert = .newChatSomeAlert(alert: .someAlert( - alert: mkAlert(title: "Invalid QR code", message: "Error scanning code: \(e.localizedDescription)"), - id: "processQRCode: failure" - )) } } - private func connect(_ link: String) { - planAndConnect( - link, - showAlert: { alert = .planAndConnectAlert(alert: $0) }, - showActionSheet: { sheet = $0 }, - dismiss: true, - incognito: nil - ) + func askCameraAuthorization(_ cb: (() -> Void)? = nil) { + AVCaptureDevice.requestAccess(for: .video) { allowed in + cameraAuthorizationStatus = AVCaptureDevice.authorizationStatus(for: .video) + if allowed { cb?() } + } } } diff --git a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift index 3f835e25d4..0ee6baa765 100644 --- a/apps/ios/Shared/Views/Onboarding/CreateProfile.swift +++ b/apps/ios/Shared/Views/Onboarding/CreateProfile.swift @@ -166,8 +166,10 @@ private func createProfile(_ displayName: String, showAlert: (UserProfileAlert) ) let m = ChatModel.shared do { + AppChatState.shared.set(.active) m.currentUser = try apiCreateActiveUser(profile) - if m.users.isEmpty { + // .isEmpty check is redundant here, but it makes it clearer what is going on + if m.users.isEmpty || m.users.allSatisfy({ $0.user.hidden }) { try startChat() withAnimation { onboardingStageDefault.set(.step3_CreateSimpleXAddress) diff --git a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift index ce1d727b10..94e281be7d 100644 --- a/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift +++ b/apps/ios/Shared/Views/Onboarding/SimpleXInfo.swift @@ -7,6 +7,7 @@ // import SwiftUI +import SimpleXChat struct SimpleXInfo: View { @EnvironmentObject var m: ChatModel @@ -44,6 +45,15 @@ struct SimpleXInfo: View { if onboarding { OnboardingActionButton() Spacer() + + Button { + m.migrationState = .pasteOrScanLink + } label: { + Label("Migrate from another device", systemImage: "tray.and.arrow.down") + .font(.subheadline) + } + .padding(.bottom, 8) + .frame(maxWidth: .infinity) } Button { @@ -54,9 +64,24 @@ struct SimpleXInfo: View { } .padding(.bottom, 8) .frame(maxWidth: .infinity) + } .frame(minHeight: g.size.height) } + .sheet(isPresented: Binding( + get: { m.migrationState != nil }, + set: { _ in + m.migrationState = nil + MigrationToDeviceState.save(nil) } + )) { + NavigationView { + VStack(alignment: .leading) { + MigrateToDevice(migrationState: $m.migrationState) + } + .navigationTitle("Migrate here") + .background(colorScheme == .light ? Color(uiColor: .tertiarySystemGroupedBackground) : .clear) + } + } .sheet(isPresented: $showHowItWorks) { HowItWorks(onboarding: onboarding) } @@ -87,6 +112,7 @@ struct SimpleXInfo: View { struct OnboardingActionButton: View { @EnvironmentObject var m: ChatModel + @Environment(\.colorScheme) var colorScheme var body: some View { if m.currentUser == nil { @@ -111,6 +137,21 @@ struct OnboardingActionButton: View { .frame(maxWidth: .infinity) .padding(.bottom) } + + private func actionButton(_ label: LocalizedStringKey, action: @escaping () -> Void) -> some View { + Button { + withAnimation { + action() + } + } label: { + HStack { + Text(label).font(.title2) + Image(systemName: "greaterthan") + } + } + .frame(maxWidth: .infinity) + .padding(.bottom) + } } struct SimpleXInfo_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index ece10e46dd..73ea5720c6 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -344,6 +344,37 @@ private let versionDescriptions: [VersionDescription] = [ description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" ), ] + ), + VersionDescription( + version: "v5.6", + post: URL(string: "https://simplex.chat/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.html"), + features: [ + FeatureDescription( + icon: "key", + title: "Quantum resistant encryption", + description: "Enable in direct chats (BETA)!" + ), + FeatureDescription( + icon: "tray.and.arrow.up", + title: "App data migration", + description: "Migrate to another device via QR code." + ), + FeatureDescription( + icon: "phone", + title: "Picture-in-picture calls", + description: "Use the app while in the call." + ), + FeatureDescription( + icon: "hand.raised", + title: "Safer groups", + description: "Admins can block a member for all." + ), + FeatureDescription( + icon: "character", + title: "Hungarian interface", + description: "Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" + ), + ] ) ] diff --git a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift index 6809dc1385..3059b049a3 100644 --- a/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift +++ b/apps/ios/Shared/Views/RemoteAccess/ConnectDesktopView.swift @@ -58,7 +58,7 @@ struct ConnectDesktopView: View { var body: some View { if viaSettings { viewBody - .modifier(BackButton(label: "Back") { + .modifier(BackButton(label: "Back", disabled: Binding.constant(false)) { if m.activeRemoteCtrl { alert = .disconnectDesktop(action: .back) } else { diff --git a/apps/ios/Shared/Views/UserSettings/AppSettings.swift b/apps/ios/Shared/Views/UserSettings/AppSettings.swift new file mode 100644 index 0000000000..ba192b333c --- /dev/null +++ b/apps/ios/Shared/Views/UserSettings/AppSettings.swift @@ -0,0 +1,72 @@ +// +// AppSettings.swift +// SimpleX (iOS) +// +// Created by Avently on 26.02.2024. +// Copyright © 2024 SimpleX Chat. All rights reserved. +// + +import Foundation +import SimpleXChat +import SwiftUI + +extension AppSettings { + public func importIntoApp() { + let def = UserDefaults.standard + if var val = networkConfig { + // migrating from Android/desktop BUT shouldn't be here ever because it should be changed in migration stage + if case .onionViaSocks = val.hostMode { + val.hostMode = .publicHost + val.requiredHostMode = true + } + val.socksProxy = nil + setNetCfg(val) + } + if let val = privacyEncryptLocalFiles { privacyEncryptLocalFilesGroupDefault.set(val) } + if let val = privacyAcceptImages { + privacyAcceptImagesGroupDefault.set(val) + def.setValue(val, forKey: DEFAULT_PRIVACY_ACCEPT_IMAGES) + } + if let val = privacyLinkPreviews { def.setValue(val, forKey: DEFAULT_PRIVACY_LINK_PREVIEWS) } + if let val = privacyShowChatPreviews { def.setValue(val, forKey: DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) } + if let val = privacySaveLastDraft { def.setValue(val, forKey: DEFAULT_PRIVACY_SAVE_LAST_DRAFT) } + if let val = privacyProtectScreen { def.setValue(val, forKey: DEFAULT_PRIVACY_PROTECT_SCREEN) } + if let val = notificationMode { ChatModel.shared.notificationMode = val.toNotificationsMode() } + if let val = notificationPreviewMode { ntfPreviewModeGroupDefault.set(val) } + if let val = webrtcPolicyRelay { def.setValue(val, forKey: DEFAULT_WEBRTC_POLICY_RELAY) } + if let val = webrtcICEServers { def.setValue(val, forKey: DEFAULT_WEBRTC_ICE_SERVERS) } + if let val = confirmRemoteSessions { def.setValue(val, forKey: DEFAULT_CONFIRM_REMOTE_SESSIONS) } + if let val = connectRemoteViaMulticast { def.setValue(val, forKey: DEFAULT_CONNECT_REMOTE_VIA_MULTICAST) } + if let val = connectRemoteViaMulticastAuto { def.setValue(val, forKey: DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO) } + if let val = developerTools { def.setValue(val, forKey: DEFAULT_DEVELOPER_TOOLS) } + if let val = confirmDBUpgrades { confirmDBUpgradesGroupDefault.set(val) } + if let val = androidCallOnLockScreen { def.setValue(val.rawValue, forKey: ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN) } + if let val = iosCallKitEnabled { callKitEnabledGroupDefault.set(val) } + if let val = iosCallKitCallsInRecents { def.setValue(val, forKey: DEFAULT_CALL_KIT_CALLS_IN_RECENTS) } + } + + public static var current: AppSettings { + let def = UserDefaults.standard + var c = AppSettings.defaults + c.networkConfig = getNetCfg() + c.privacyEncryptLocalFiles = privacyEncryptLocalFilesGroupDefault.get() + c.privacyAcceptImages = privacyAcceptImagesGroupDefault.get() + c.privacyLinkPreviews = def.bool(forKey: DEFAULT_PRIVACY_LINK_PREVIEWS) + c.privacyShowChatPreviews = def.bool(forKey: DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS) + c.privacySaveLastDraft = def.bool(forKey: DEFAULT_PRIVACY_SAVE_LAST_DRAFT) + c.privacyProtectScreen = def.bool(forKey: DEFAULT_PRIVACY_PROTECT_SCREEN) + c.notificationMode = AppSettingsNotificationMode.from(ChatModel.shared.notificationMode) + c.notificationPreviewMode = ntfPreviewModeGroupDefault.get() + c.webrtcPolicyRelay = def.bool(forKey: DEFAULT_WEBRTC_POLICY_RELAY) + c.webrtcICEServers = def.stringArray(forKey: DEFAULT_WEBRTC_ICE_SERVERS) + c.confirmRemoteSessions = def.bool(forKey: DEFAULT_CONFIRM_REMOTE_SESSIONS) + c.connectRemoteViaMulticast = def.bool(forKey: DEFAULT_CONNECT_REMOTE_VIA_MULTICAST) + c.connectRemoteViaMulticastAuto = def.bool(forKey: DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO) + c.developerTools = def.bool(forKey: DEFAULT_DEVELOPER_TOOLS) + c.confirmDBUpgrades = confirmDBUpgradesGroupDefault.get() + c.androidCallOnLockScreen = AppSettingsLockScreenCalls(rawValue: def.string(forKey: ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN)!) + c.iosCallKitEnabled = callKitEnabledGroupDefault.get() + c.iosCallKitCallsInRecents = def.bool(forKey: DEFAULT_CALL_KIT_CALLS_IN_RECENTS) + return c + } +} diff --git a/apps/ios/Shared/Views/UserSettings/DeveloperView.swift b/apps/ios/Shared/Views/UserSettings/DeveloperView.swift index 3bbfbfe33e..9b11c6d0f7 100644 --- a/apps/ios/Shared/Views/UserSettings/DeveloperView.swift +++ b/apps/ios/Shared/Views/UserSettings/DeveloperView.swift @@ -12,6 +12,7 @@ import SimpleXChat struct DeveloperView: View { @AppStorage(DEFAULT_DEVELOPER_TOOLS) private var developerTools = false @AppStorage(GROUP_DEFAULT_CONFIRM_DB_UPGRADES, store: groupDefaults) private var confirmDatabaseUpgrades = false + @AppStorage(GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED, store: groupDefaults) private var pqExperimentalEnabled = false @Environment(\.colorScheme) var colorScheme var body: some View { @@ -42,9 +43,33 @@ struct DeveloperView: View { } footer: { (developerTools ? Text("Show:") : Text("Hide:")) + Text(" ") + Text("Database IDs and Transport isolation option.") } + + if developerTools { + Section { + settingsRow("key") { + Toggle("Post-quantum E2EE", isOn: $pqExperimentalEnabled) + .onChange(of: pqExperimentalEnabled) { + setPQExperimentalEnabled($0) + } + } + } header: { + Text(String("Experimental")) + } footer: { + Text(String("In this version applies only to new contacts.")) + } + } } } } + + private func setPQExperimentalEnabled(_ enable: Bool) { + do { + try apiSetPQEncryption(enable) + } catch let error { + let err = responseError(error) + logger.error("apiSetPQEncryption \(err)") + } + } } struct DeveloperView_Previews: PreviewProvider { diff --git a/apps/ios/Shared/Views/UserSettings/NotificationsView.swift b/apps/ios/Shared/Views/UserSettings/NotificationsView.swift index 04c02f0dd2..4876d60eca 100644 --- a/apps/ios/Shared/Views/UserSettings/NotificationsView.swift +++ b/apps/ios/Shared/Views/UserSettings/NotificationsView.swift @@ -76,6 +76,10 @@ struct NotificationsView: View { Text(m.notificationPreview.label) } } + + if let server = m.notificationServer { + smpServers("Push server", [server]) + } } header: { Text("Push notifications") } footer: { @@ -87,6 +91,9 @@ struct NotificationsView: View { } } .disabled(legacyDatabase) + .onAppear { + (m.savedToken, m.tokenStatus, m.notificationMode, m.notificationServer) = apiGetNtfToken() + } } private func notificationAlert(_ alert: NotificationAlert, _ token: DeviceToken) -> Alert { @@ -125,6 +132,7 @@ struct NotificationsView: View { m.tokenStatus = .new notificationMode = .off m.notificationMode = .off + m.notificationServer = nil } } catch let error { await MainActor.run { @@ -135,11 +143,13 @@ struct NotificationsView: View { } default: do { - let status = try await apiRegisterToken(token: token, notificationMode: mode) + let _ = try await apiRegisterToken(token: token, notificationMode: mode) + let (_, tknStatus, ntfMode, ntfServer) = apiGetNtfToken() await MainActor.run { - m.tokenStatus = status - notificationMode = mode - m.notificationMode = mode + m.tokenStatus = tknStatus + notificationMode = ntfMode + m.notificationMode = ntfMode + m.notificationServer = ntfServer } } catch let error { await MainActor.run { diff --git a/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift b/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift index 48d5a66970..6702ab7ce8 100644 --- a/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift +++ b/apps/ios/Shared/Views/UserSettings/ProtocolServerView.swift @@ -31,7 +31,7 @@ struct ProtocolServerView: View { ProgressView().scaleEffect(2) } } - .modifier(BackButton(label: "Your \(proto) servers") { + .modifier(BackButton(label: "Your \(proto) servers", disabled: Binding.constant(false)) { server = serverToEdit dismiss() }) @@ -117,6 +117,7 @@ struct ProtocolServerView: View { struct BackButton: ViewModifier { var label: LocalizedStringKey = "Back" + @Binding var disabled: Bool var action: () -> Void func body(content: Content) -> some View { @@ -130,6 +131,7 @@ struct BackButton: ViewModifier { Text(label) } } + .disabled(disabled) } } } diff --git a/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift b/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift index 382eaffbef..b9163d4bad 100644 --- a/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift +++ b/apps/ios/Shared/Views/UserSettings/ProtocolServersView.swift @@ -95,7 +95,7 @@ struct ProtocolServersView: View { .sheet(isPresented: $showScanProtoServer) { ScanProtocolServer(servers: $servers) } - .modifier(BackButton { + .modifier(BackButton(disabled: Binding.constant(false)) { if saveDisabled { dismiss() justOpened = false diff --git a/apps/ios/Shared/Views/UserSettings/SettingsView.swift b/apps/ios/Shared/Views/UserSettings/SettingsView.swift index a691e6afc9..1799d8136a 100644 --- a/apps/ios/Shared/Views/UserSettings/SettingsView.swift +++ b/apps/ios/Shared/Views/UserSettings/SettingsView.swift @@ -27,7 +27,7 @@ let DEFAULT_NOTIFICATION_ALERT_SHOWN = "notificationAlertShown" let DEFAULT_WEBRTC_POLICY_RELAY = "webrtcPolicyRelay" let DEFAULT_WEBRTC_ICE_SERVERS = "webrtcICEServers" let DEFAULT_CALL_KIT_CALLS_IN_RECENTS = "callKitCallsInRecents" -let DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" +let DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" // unused. Use GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES instead let DEFAULT_PRIVACY_LINK_PREVIEWS = "privacyLinkPreviews" let DEFAULT_PRIVACY_SIMPLEX_LINK_MODE = "privacySimplexLinkMode" let DEFAULT_PRIVACY_SHOW_CHAT_PREVIEWS = "privacyShowChatPreviews" @@ -51,6 +51,8 @@ let DEFAULT_SHOW_HIDDEN_PROFILES_NOTICE = "showHiddenProfilesNotice" let DEFAULT_SHOW_MUTE_PROFILE_ALERT = "showMuteProfileAlert" let DEFAULT_WHATS_NEW_VERSION = "defaultWhatsNewVersion" let DEFAULT_ONBOARDING_STAGE = "onboardingStage" +let DEFAULT_MIGRATION_TO_STAGE = "migrationToStage" +let DEFAULT_MIGRATION_FROM_STAGE = "migrationFromStage" let DEFAULT_CUSTOM_DISAPPEARING_MESSAGE_TIME = "customDisappearingMessageTime" let DEFAULT_SHOW_UNREAD_AND_FAVORITES = "showUnreadAndFavorites" let DEFAULT_DEVICE_NAME_FOR_REMOTE_ACCESS = "deviceNameForRemoteAccess" @@ -58,6 +60,8 @@ let DEFAULT_CONFIRM_REMOTE_SESSIONS = "confirmRemoteSessions" let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST = "connectRemoteViaMulticast" let DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO = "connectRemoteViaMulticastAuto" +let ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN = "androidCallOnLockScreen" + let appDefaults: [String: Any] = [ DEFAULT_SHOW_LA_NOTICE: false, DEFAULT_LA_NOTICE_SHOWN: false, @@ -93,6 +97,7 @@ let appDefaults: [String: Any] = [ DEFAULT_CONFIRM_REMOTE_SESSIONS: false, DEFAULT_CONNECT_REMOTE_VIA_MULTICAST: true, DEFAULT_CONNECT_REMOTE_VIA_MULTICAST_AUTO: true, + ANDROID_DEFAULT_CALL_ON_LOCK_SCREEN: AppSettingsLockScreenCalls.show.rawValue ] // not used anymore @@ -148,10 +153,14 @@ struct SettingsView: View { @EnvironmentObject var chatModel: ChatModel @EnvironmentObject var sceneDelegate: SceneDelegate @Binding var showSettings: Bool + @State private var showProgress: Bool = false var body: some View { ZStack { settingsView() + if showProgress { + progressView() + } if let la = chatModel.laRequest { LocalAuthView(authRequest: la) } @@ -202,9 +211,17 @@ struct SettingsView: View { } label: { settingsRow("desktopcomputer") { Text("Use from desktop") } } + + NavigationLink { + MigrateFromDevice(showSettings: $showSettings, showProgressOnSettings: $showProgress) + .navigationTitle("Migrate device") + .navigationBarTitleDisplayMode(.large) + } label: { + settingsRow("tray.and.arrow.up") { Text("Migrate to another device") } + } } .disabled(chatModel.chatRunning != true) - + Section("Settings") { NavigationLink { NotificationsView() @@ -349,6 +366,13 @@ struct SettingsView: View { } } + private func progressView() -> some View { + VStack { + ProgressView().scaleEffect(2) + } + .frame(maxWidth: .infinity, maxHeight: .infinity ) + } + private enum NotificationAlert { case enable case error(LocalizedStringKey, String) diff --git a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift index e9657961ef..96eeffd16d 100644 --- a/apps/ios/Shared/Views/UserSettings/UserAddressView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserAddressView.swift @@ -47,7 +47,7 @@ struct UserAddressView: View { userAddressScrollView() } else { userAddressScrollView() - .modifier(BackButton { + .modifier(BackButton(disabled: Binding.constant(false)) { if savedAAS == aas { dismiss() } else { diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift index f2cac59dae..af3d60c9c0 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift @@ -276,6 +276,7 @@ struct UserProfilesView: View { // Deleting the last visible user while having hidden one(s) try await deleteUser() try await changeActiveUserAsync_(nil, viewPwd: nil) + try? await stopChatAsync() await MainActor.run { onboardingStageDefault.set(.step1_SimpleXInfo) m.onboardingStage = .step1_SimpleXInfo diff --git a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff index 85e4ebb985..f04880ee91 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -107,6 +107,10 @@ %@ свързан No comment provided by engineer. + + %@ downloaded + No comment provided by engineer. + %@ is connected! %@ е свързан! @@ -127,6 +131,10 @@ %@ сървъри No comment provided by engineer. + + %@ uploaded + No comment provided by engineer. + %@ wants to connect! %@ иска да се свърже! @@ -219,6 +227,7 @@ %lld messages blocked by admin + %lld съобщения, блокирани от администратора No comment provided by engineer. @@ -341,6 +350,10 @@ **Най-поверително**: не използвайте сървъра за известия SimpleX Chat, периодично проверявайте съобщенията във фонов режим (зависи от това колко често използвате приложението). No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Моля, обърнете внимание**: НЯМА да можете да възстановите или промените паролата, ако я загубите. @@ -356,6 +369,10 @@ **Внимание**: Незабавните push известия изискват парола, запазена в Keychain. No comment provided by engineer. + + **Warning**: the archive will be removed. + No comment provided by engineer. + **e2e encrypted** audio call **e2e криптиран**аудио разговор @@ -612,6 +629,10 @@ Промяната на адреса ще бъде прекъсната. Ще се използва старият адрес за получаване. No comment provided by engineer. + + Admins can block a member for all. + No comment provided by engineer. + Admins can create the links to join groups. Админите могат да създадат линкове за присъединяване към групи. @@ -644,6 +665,7 @@ All messages will be deleted - this cannot be undone! + Всички съобщения ще бъдат изтрити - това не може да бъде отменено! No comment provided by engineer. @@ -666,6 +688,10 @@ Всички ваши контакти ще останат свързани. Актуализацията на профила ще бъде изпратена до вашите контакти. No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + No comment provided by engineer. + Allow Позволи @@ -791,6 +817,10 @@ Компилация на приложението: %@ No comment provided by engineer. + + App data migration + No comment provided by engineer. + App encrypts new local files (except videos). Приложението криптира нови локални файлове (с изключение на видеоклипове). @@ -826,6 +856,18 @@ Изглед No comment provided by engineer. + + Apply + No comment provided by engineer. + + + Archive and upload + No comment provided by engineer. + + + Archiving database + No comment provided by engineer. + Attach Прикачи @@ -923,6 +965,7 @@ Block for all + Блокирай за всички No comment provided by engineer. @@ -937,6 +980,7 @@ Block member for all? + Блокиране на член за всички? No comment provided by engineer. @@ -946,6 +990,7 @@ Blocked by admin + Блокиран от админ No comment provided by engineer. @@ -1013,6 +1058,10 @@ Отказ No comment provided by engineer. + + Cancel migration + No comment provided by engineer. + Cannot access keychain to save database password Няма достъп до Keychain за запазване на паролата за базата данни @@ -1114,6 +1163,10 @@ Чатът е спрян. Ако вече сте използвали тази база данни на друго устройство, трябва да я прехвърлите обратно, преди да стартирате чата отново. No comment provided by engineer. + + Chat migrated! + No comment provided by engineer. + Chat preferences Чат настройки @@ -1134,6 +1187,10 @@ Китайски и Испански интерфейс No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + No comment provided by engineer. + Choose file Избери файл @@ -1161,6 +1218,7 @@ Clear private notes? + Изчистване на лични бележки? No comment provided by engineer. @@ -1203,6 +1261,10 @@ Потвърди актуализаациите на базата данни No comment provided by engineer. + + Confirm network settings + No comment provided by engineer. + Confirm new passphrase… Потвърди новата парола… @@ -1213,6 +1275,14 @@ Потвърди парола No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + No comment provided by engineer. + + + Confirm upload + No comment provided by engineer. + Connect Свързване @@ -1459,10 +1529,12 @@ This is your own one-time link! Created at + Създаден на No comment provided by engineer. Created at: %@ + Създаден на: %@ copied message info @@ -1470,6 +1542,10 @@ This is your own one-time link! Създаден на %@ No comment provided by engineer. + + Creating archive link + No comment provided by engineer. + Creating link… Линкът се създава… @@ -1690,6 +1766,10 @@ This cannot be undone! Изтрий базата данни No comment provided by engineer. + + Delete database from this device + No comment provided by engineer. + Delete file Изтрий файл @@ -1942,7 +2022,7 @@ This cannot be undone! Discover via local network - Открий през локалната мрежа + Откриване през локалната мрежа No comment provided by engineer. @@ -1980,11 +2060,23 @@ This cannot be undone! Понижи версията и отвори чата No comment provided by engineer. + + Download failed + No comment provided by engineer. + Download file Свали файл server test step + + Downloading archive + No comment provided by engineer. + + + Downloading link details + No comment provided by engineer. + Duplicate display name! Дублирано име! @@ -2040,6 +2132,10 @@ This cannot be undone! Активиране за всички No comment provided by engineer. + + Enable in direct chats (BETA)! + No comment provided by engineer. + Enable instant notifications? Активирай незабавни известия? @@ -2155,6 +2251,10 @@ This cannot be undone! Въведи име на групата… No comment provided by engineer. + + Enter passphrase + No comment provided by engineer. + Enter passphrase… Въведи парола… @@ -2215,6 +2315,10 @@ This cannot be undone! Грешка при добавяне на член(ове) No comment provided by engineer. + + Error allowing contact PQ encryption + No comment provided by engineer. + Error changing address Грешка при промяна на адреса @@ -2252,6 +2356,7 @@ This cannot be undone! Error creating message + Грешка при създаване на съобщение No comment provided by engineer. @@ -2304,6 +2409,10 @@ This cannot be undone! Грешка при изтриване на потребителския профил No comment provided by engineer. + + Error downloading the archive + No comment provided by engineer. + Error enabling delivery receipts! Грешка при активирането на потвърждениeто за доставка! @@ -2379,6 +2488,10 @@ This cannot be undone! Грешка при запазване на парола в Кeychain No comment provided by engineer. + + Error saving settings + when migrating + Error saving user password Грешка при запазване на потребителска парола @@ -2449,6 +2562,14 @@ This cannot be undone! Грешка при актуализиране на поверителността на потребителя No comment provided by engineer. + + Error uploading the archive + No comment provided by engineer. + + + Error verifying passphrase: + No comment provided by engineer. + Error: Грешка: @@ -2499,6 +2620,10 @@ This cannot be undone! Експортиран архив на базата данни. No comment provided by engineer. + + Exported file doesn't exist + No comment provided by engineer. + Exporting database archive… Експортиране на архив на базата данни… @@ -2569,6 +2694,14 @@ This cannot be undone! Филтрирайте непрочетените и любимите чатове. No comment provided by engineer. + + Finalize migration + No comment provided by engineer. + + + Finalize migration on another device. + No comment provided by engineer. + Finally, we have them! 🚀 Най-накрая ги имаме! 🚀 @@ -2859,6 +2992,10 @@ This cannot be undone! Как да използвате вашите сървъри No comment provided by engineer. + + Hungarian interface + No comment provided by engineer. + ICE servers (one per line) ICE сървъри (по един на ред) @@ -2924,8 +3061,17 @@ This cannot be undone! Импортиране на база данни No comment provided by engineer. + + Import failed + No comment provided by engineer. + + + Importing archive + No comment provided by engineer. + Improved message delivery + Подобрена доставка на съобщения No comment provided by engineer. @@ -2938,6 +3084,10 @@ This cannot be undone! Подобрена конфигурация на сървъра No comment provided by engineer. + + In order to continue, chat should be stopped. + No comment provided by engineer. + In reply to В отговор на @@ -3050,6 +3200,10 @@ This cannot be undone! Невалиден линк No comment provided by engineer. + + Invalid migration confirmation + No comment provided by engineer. + Invalid name! Невалидно име! @@ -3153,6 +3307,7 @@ This cannot be undone! Join group conversations + Присъединяване към групи No comment provided by engineer. @@ -3417,6 +3572,10 @@ This is your link for group %@! Текст на съобщението No comment provided by engineer. + + Message too large + No comment provided by engineer. + Messages Съобщения @@ -3432,11 +3591,47 @@ This is your link for group %@! Съобщенията от %@ ще бъдат показани! 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. + No comment provided by engineer. + + + Migrate device + No comment provided by engineer. + + + Migrate from another device + No comment provided by engineer. + + + Migrate here + No comment provided by engineer. + + + Migrate to another device + No comment provided by engineer. + + + Migrate to another device via QR code. + No comment provided by engineer. + + + Migrating + No comment provided by engineer. + Migrating database archive… Архивът на базата данни се мигрира… No comment provided by engineer. + + Migration complete + No comment provided by engineer. + Migration error: Грешка при мигриране: @@ -3796,6 +3991,10 @@ This is your link for group %@! Отвори група No comment provided by engineer. + + Open migration to another device + authentication reason + Open user profiles Отвори потребителските профили @@ -3811,11 +4010,19 @@ This is your link for group %@! Приложението се отваря… No comment provided by engineer. + + Or paste archive link + No comment provided by engineer. + Or scan QR code Или сканирай QR код No comment provided by engineer. + + Or securely share this file link + No comment provided by engineer. + Or show this code Или покажи този код @@ -3863,6 +4070,7 @@ This is your link for group %@! Past member %@ + Бивш член %@ past/unknown group member @@ -3877,6 +4085,7 @@ This is your link for group %@! Paste link to connect! + Поставете линк, за да се свържете! No comment provided by engineer. @@ -3899,6 +4108,10 @@ This is your link for group %@! Постоянна грешка при декриптиране message decrypt error item + + Picture-in-picture calls + No comment provided by engineer. + Please ask your contact to enable sending voice messages. Моля, попитайте вашия контакт, за да активирате изпращане на гласови съобщения. @@ -3919,6 +4132,10 @@ This is your link for group %@! Моля, проверете вашите настройки и тези вашия за контакт. No comment provided by engineer. + + Please confirm that network settings are correct for this device. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3976,6 +4193,10 @@ Error: %@ Въжможно е пръстовият отпечатък на сертификата в адреса на сървъра да е неправилен server test error + + Post-quantum E2EE + No comment provided by engineer. + Preserve the last message draft, with attachments. Запазете последната чернова на съобщението с прикачени файлове. @@ -4013,6 +4234,7 @@ Error: %@ Private notes + Лични бележки name of notes to self @@ -4110,6 +4332,14 @@ Error: %@ Push известия No comment provided by engineer. + + Push server + No comment provided by engineer. + + + Quantum resistant encryption + No comment provided by engineer. + Rate the app Оценете приложението @@ -4197,6 +4427,7 @@ Error: %@ Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). + Скорошна история и подобрен [bot за директория за групи](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPd jdLW3%23%2F%3Fv%3D1-2% 26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). No comment provided by engineer. @@ -4294,11 +4525,23 @@ Error: %@ Изпрати отново заявката за свързване? No comment provided by engineer. + + Repeat download + No comment provided by engineer. + + + Repeat import + No comment provided by engineer. + Repeat join request? Изпрати отново заявката за присъединяване? No comment provided by engineer. + + Repeat upload + No comment provided by engineer. + Reply Отговори @@ -4399,6 +4642,10 @@ Error: %@ SMP сървъри No comment provided by engineer. + + Safer groups + No comment provided by engineer. + Save Запази @@ -4486,6 +4733,7 @@ Error: %@ Saved message + Запазено съобщение message info title @@ -4520,6 +4768,7 @@ Error: %@ Search bar accepts invitation links. + Лентата за търсене приема линк за връзка. No comment provided by engineer. @@ -4762,6 +5011,10 @@ Error: %@ Задай kод за достъп No comment provided by engineer. + + Set passphrase + No comment provided by engineer. + Set passphrase to export Задай парола за експортиране @@ -4817,6 +5070,10 @@ Error: %@ Сподели с контактите No comment provided by engineer. + + Show QR code + No comment provided by engineer. + Show calls in phone history Показване на обажданията в хронологията на телефона @@ -4957,6 +5214,10 @@ Error: %@ Спри SimpleX authentication reason + + Stop chat + No comment provided by engineer. + Stop chat to enable database actions Спрете чата, за да активирате действията с базата данни @@ -4997,6 +5258,10 @@ Error: %@ Спри споделянето на адреса? No comment provided by engineer. + + Stopping chat + No comment provided by engineer. + Submit Изпрати @@ -5244,6 +5509,14 @@ It can happen because of some bug or when the connection is compromised.Това действие не може да бъде отменено - вашият профил, контакти, съобщения и файлове ще бъдат безвъзвратно загубени. No comment provided by engineer. + + This chat is protected by end-to-end encryption. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + E2EE info chat item + This device name Името на това устройство @@ -5358,6 +5631,7 @@ You will be prompted to complete authentication before this feature is enabled.< Turkish interface + Турски интерфейс No comment provided by engineer. @@ -5382,6 +5656,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock for all + Отблокирай за всички No comment provided by engineer. @@ -5391,6 +5666,7 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock member for all? + Отблокиране на член за всички? No comment provided by engineer. @@ -5535,11 +5811,19 @@ To connect, please ask your contact to create another connection link and check Актуализирай и отвори чата No comment provided by engineer. + + Upload failed + No comment provided by engineer. + Upload file Качи файл server test step + + Uploading archive + No comment provided by engineer. + Use .onion hosts Използвай .onion хостове @@ -5590,6 +5874,10 @@ To connect, please ask your contact to create another connection link and check Използвай сървър No comment provided by engineer. + + Use the app while in the call. + No comment provided by engineer. + User profile Потребителски профил @@ -5607,27 +5895,35 @@ To connect, please ask your contact to create another connection link and check Verify code with desktop - Потвръди кода с настолното устройство + Потвърди кода с настолното устройство No comment provided by engineer. Verify connection - Потвръди връзките + Потвърди връзка No comment provided by engineer. Verify connection security - Потвръди сигурността на връзката + Потвърди сигурността на връзката No comment provided by engineer. Verify connections - Потвръди връзките + Потвърждение за свързване + No comment provided by engineer. + + + Verify database passphrase + No comment provided by engineer. + + + Verify passphrase No comment provided by engineer. Verify security code - Потвръди кода за сигурност + Потвърди кода за сигурност No comment provided by engineer. @@ -5715,6 +6011,10 @@ To connect, please ask your contact to create another connection link and check Изчаква се получаването на видеото No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + No comment provided by engineer. + Warning: you may lose some data! Предупреждение: Може да загубите някои данни! @@ -5735,6 +6035,10 @@ To connect, please ask your contact to create another connection link and check Съобщение при посрещане No comment provided by engineer. + + Welcome message is too long + No comment provided by engineer. + What's new Какво е новото @@ -5757,6 +6061,7 @@ To connect, please ask your contact to create another connection link and check With encrypted files and media. + С криптирани файлове и медия. No comment provided by engineer. @@ -5766,6 +6071,7 @@ To connect, please ask your contact to create another connection link and check With reduced battery usage. + С намален разход на батерията. No comment provided by engineer. @@ -5788,6 +6094,10 @@ To connect, please ask your contact to create another connection link and check Вие No comment provided by engineer. + + You **must not** use the same database on two devices. + No comment provided by engineer. + You accepted connection Вие приехте връзката @@ -5875,6 +6185,10 @@ Repeat join request? Можете да ги активирате по-късно през настройките за "Поверителност и сигурност" на приложението. No comment provided by engineer. + + You can give another try. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. Можете да скриете или заглушите известията за потребителски профил - плъзнете надясно. @@ -6269,15 +6583,17 @@ SimpleX сървърите не могат да видят вашия профи blocked блокиран - No comment provided by engineer. + marked deleted chat item preview text blocked %@ + блокиран %@ rcv group event chat item blocked by admin - blocked chat item + блокиран от админ + marked deleted chat item preview text bold @@ -6306,7 +6622,7 @@ SimpleX сървърите не могат да видят вашия профи changed address for you - променен е адреса за вас + адреса за изпращане е променен chat item text @@ -6401,6 +6717,7 @@ SimpleX сървърите не могат да видят вашия профи contact %1$@ changed to %2$@ + името на контакта %1$@ е променено на %2$@ profile update event chat item @@ -6675,6 +6992,7 @@ SimpleX сървърите не могат да видят вашия профи member %1$@ changed to %2$@ + името на члена %1$@ е променено на %2$@ profile update event chat item @@ -6705,7 +7023,7 @@ SimpleX сървърите не могат да видят вашия профи moderated by %@ модерирано от %@ - No comment provided by engineer. + marked deleted chat item preview text months @@ -6774,6 +7092,10 @@ SimpleX сървърите не могат да видят вашия профи peer-to-peer No comment provided by engineer. + + quantum resistant e2e encryption + chat item text + received answer… получен отговор… @@ -6801,10 +7123,12 @@ SimpleX сървърите не могат да видят вашия профи removed contact address + премахнат адрес за контакт profile update event chat item removed profile picture + премахната профилна снимка profile update event chat item @@ -6839,12 +7163,18 @@ SimpleX сървърите не могат да видят вашия профи set new contact address + зададен нов адрес за контакт profile update event chat item set new profile picture + зададена нова профилна снимка profile update event chat item + + standard end-to-end encryption + chat item text + starting… стартиране… @@ -6862,6 +7192,7 @@ SimpleX сървърите не могат да видят вашия профи unblocked %@ + отблокиран %@ rcv group event chat item @@ -6871,6 +7202,7 @@ SimpleX сървърите не могат да видят вашия профи unknown status + неизвестен статус No comment provided by engineer. @@ -6880,6 +7212,7 @@ SimpleX сървърите не могат да видят вашия профи updated profile + актуализиран профил profile update event chat item @@ -6954,16 +7287,17 @@ SimpleX сървърите не могат да видят вашия профи you blocked %@ + вие блокирахте %@ snd group event chat item you changed address - променихте адреса + адреса за получаване е променен chat item text you changed address for %@ - променихте адреса за %@ + променихте адреса получаване за %@ chat item text @@ -6998,6 +7332,7 @@ SimpleX сървърите не могат да видят вашия профи you unblocked %@ + вие отблокирахте %@ snd group event chat item diff --git a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff index a376c86ad1..cf6e0e1fcd 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -107,6 +107,10 @@ %@ připojen No comment provided by engineer. + + %@ downloaded + No comment provided by engineer. + %@ is connected! %@ je připojen! @@ -127,6 +131,10 @@ %@ servery No comment provided by engineer. + + %@ uploaded + No comment provided by engineer. + %@ wants to connect! %@ se chce připojit! @@ -332,6 +340,10 @@ **Nejsoukromější**: nepoužívejte server oznámení SimpleX Chat, pravidelně kontrolujte zprávy na pozadí (závisí na tom, jak často aplikaci používáte). No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Upozornění**: Pokud heslo ztratíte, NEBUDETE jej moci obnovit ani změnit. @@ -347,6 +359,10 @@ **Upozornění**: Okamžitě doručovaná oznámení vyžadují přístupové heslo uložené v Klíčence. No comment provided by engineer. + + **Warning**: the archive will be removed. + No comment provided by engineer. + **e2e encrypted** audio call **e2e šifrovaný** audio hovor @@ -598,6 +614,10 @@ Změna adresy bude přerušena. Budou použity staré přijímací adresy. No comment provided by engineer. + + Admins can block a member for all. + No comment provided by engineer. + Admins can create the links to join groups. Správci mohou vytvářet odkazy pro připojení ke skupinám. @@ -651,6 +671,10 @@ Všechny vaše kontakty zůstanou připojeny. Aktualizace profilu bude odeslána vašim kontaktům. No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + No comment provided by engineer. + Allow Povolit @@ -774,6 +798,10 @@ Sestavení aplikace: %@ No comment provided by engineer. + + App data migration + No comment provided by engineer. + App encrypts new local files (except videos). Aplikace šifruje nové místní soubory (s výjimkou videí). @@ -809,6 +837,18 @@ Vzhled No comment provided by engineer. + + Apply + No comment provided by engineer. + + + Archive and upload + No comment provided by engineer. + + + Archiving database + No comment provided by engineer. + Attach Připojit @@ -989,6 +1029,10 @@ Zrušit No comment provided by engineer. + + Cancel migration + No comment provided by engineer. + Cannot access keychain to save database password Nelze získat přístup ke klíčence pro uložení hesla databáze @@ -1089,6 +1133,10 @@ Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. No comment provided by engineer. + + Chat migrated! + No comment provided by engineer. + Chat preferences Předvolby chatu @@ -1109,6 +1157,10 @@ Čínské a Španělské rozhranní No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + No comment provided by engineer. + Choose file Vybrat soubor @@ -1178,6 +1230,10 @@ Potvrdit aktualizaci databáze No comment provided by engineer. + + Confirm network settings + No comment provided by engineer. + Confirm new passphrase… Potvrdit novou heslovou frázi… @@ -1188,6 +1244,14 @@ Potvrdit heslo No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + No comment provided by engineer. + + + Confirm upload + No comment provided by engineer. + Connect Připojit @@ -1428,6 +1492,10 @@ This is your own one-time link! Vytvořeno na %@ No comment provided by engineer. + + Creating archive link + No comment provided by engineer. + Creating link… No comment provided by engineer. @@ -1643,6 +1711,10 @@ This cannot be undone! Odstranění databáze No comment provided by engineer. + + Delete database from this device + No comment provided by engineer. + Delete file Smazat soubor @@ -1927,11 +1999,23 @@ This cannot be undone! Snížit a otevřít chat No comment provided by engineer. + + Download failed + No comment provided by engineer. + Download file Stáhnout soubor server test step + + Downloading archive + No comment provided by engineer. + + + Downloading link details + No comment provided by engineer. + Duplicate display name! Duplicitní zobrazované jméno! @@ -1986,6 +2070,10 @@ This cannot be undone! Povolit pro všechny No comment provided by engineer. + + Enable in direct chats (BETA)! + No comment provided by engineer. + Enable instant notifications? Povolit okamžitá oznámení? @@ -2097,6 +2185,10 @@ This cannot be undone! Enter group name… No comment provided by engineer. + + Enter passphrase + No comment provided by engineer. + Enter passphrase… Zadejte přístupovou frázi… @@ -2155,6 +2247,10 @@ This cannot be undone! Chyba přidávání člena(ů) No comment provided by engineer. + + Error allowing contact PQ encryption + No comment provided by engineer. + Error changing address Chuba změny adresy @@ -2244,6 +2340,10 @@ This cannot be undone! Chyba mazání uživatelského profilu No comment provided by engineer. + + Error downloading the archive + No comment provided by engineer. + Error enabling delivery receipts! Chyba povolení potvrzení o doručení! @@ -2318,6 +2418,10 @@ This cannot be undone! Při ukládání přístupové fráze do klíčenky došlo k chybě No comment provided by engineer. + + Error saving settings + when migrating + Error saving user password Chyba ukládání hesla uživatele @@ -2387,6 +2491,14 @@ This cannot be undone! Chyba aktualizace soukromí uživatele No comment provided by engineer. + + Error uploading the archive + No comment provided by engineer. + + + Error verifying passphrase: + No comment provided by engineer. + Error: Chyba: @@ -2436,6 +2548,10 @@ This cannot be undone! Exportovaný archiv databáze. No comment provided by engineer. + + Exported file doesn't exist + No comment provided by engineer. + Exporting database archive… Exportuji archiv databáze… @@ -2505,6 +2621,14 @@ This cannot be undone! Filtrovat nepřečtené a oblíbené chaty. No comment provided by engineer. + + Finalize migration + No comment provided by engineer. + + + Finalize migration on another device. + No comment provided by engineer. + Finally, we have them! 🚀 Konečně je máme! 🚀 @@ -2790,6 +2914,10 @@ This cannot be undone! Jak používat servery No comment provided by engineer. + + Hungarian interface + No comment provided by engineer. + ICE servers (one per line) Servery ICE (jeden na řádek) @@ -2855,6 +2983,14 @@ This cannot be undone! Import databáze No comment provided by engineer. + + Import failed + No comment provided by engineer. + + + Importing archive + No comment provided by engineer. + Improved message delivery No comment provided by engineer. @@ -2869,6 +3005,10 @@ This cannot be undone! Vylepšená konfigurace serveru No comment provided by engineer. + + In order to continue, chat should be stopped. + No comment provided by engineer. + In reply to V odpovědi na @@ -2976,6 +3116,10 @@ This cannot be undone! Invalid link No comment provided by engineer. + + Invalid migration confirmation + No comment provided by engineer. + Invalid name! No comment provided by engineer. @@ -3331,6 +3475,10 @@ This is your link for group %@! Text zprávy No comment provided by engineer. + + Message too large + No comment provided by engineer. + Messages Zprávy @@ -3345,11 +3493,47 @@ This is your link for group %@! Messages from %@ will be shown! 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. + No comment provided by engineer. + + + Migrate device + No comment provided by engineer. + + + Migrate from another device + No comment provided by engineer. + + + Migrate here + No comment provided by engineer. + + + Migrate to another device + No comment provided by engineer. + + + Migrate to another device via QR code. + No comment provided by engineer. + + + Migrating + No comment provided by engineer. + Migrating database archive… Přenášení archivu databáze… No comment provided by engineer. + + Migration complete + No comment provided by engineer. + Migration error: Chyba přenášení: @@ -3705,6 +3889,10 @@ This is your link for group %@! Open group No comment provided by engineer. + + Open migration to another device + authentication reason + Open user profiles Otevřít uživatelské profily @@ -3719,10 +3907,18 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or paste archive link + No comment provided by engineer. + Or scan QR code No comment provided by engineer. + + Or securely share this file link + No comment provided by engineer. + Or show this code No comment provided by engineer. @@ -3803,6 +3999,10 @@ This is your link for group %@! Chyba dešifrování message decrypt error item + + Picture-in-picture calls + No comment provided by engineer. + Please ask your contact to enable sending voice messages. Prosím, požádejte kontaktní osobu, aby umožnila odesílání hlasových zpráv. @@ -3823,6 +4023,10 @@ This is your link for group %@! Zkontrolujte prosím nastavení své i svého kontaktu. No comment provided by engineer. + + Please confirm that network settings are correct for this device. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3878,6 +4082,10 @@ Error: %@ Je možné, že otisk certifikátu v adrese serveru je nesprávný server test error + + Post-quantum E2EE + No comment provided by engineer. + Preserve the last message draft, with attachments. Zachování posledního návrhu zprávy s přílohami. @@ -4010,6 +4218,14 @@ Error: %@ Nabízená oznámení No comment provided by engineer. + + Push server + No comment provided by engineer. + + + Quantum resistant encryption + No comment provided by engineer. + Rate the app Ohodnoťte aplikaci @@ -4192,10 +4408,22 @@ Error: %@ Repeat connection request? No comment provided by engineer. + + Repeat download + No comment provided by engineer. + + + Repeat import + No comment provided by engineer. + Repeat join request? No comment provided by engineer. + + Repeat upload + No comment provided by engineer. + Reply Odpověď @@ -4295,6 +4523,10 @@ Error: %@ SMP servery No comment provided by engineer. + + Safer groups + No comment provided by engineer. + Save Uložit @@ -4654,6 +4886,10 @@ Error: %@ Nastavit heslo No comment provided by engineer. + + Set passphrase + No comment provided by engineer. + Set passphrase to export Nastavení přístupové fráze pro export @@ -4708,6 +4944,10 @@ Error: %@ Sdílet s kontakty No comment provided by engineer. + + Show QR code + No comment provided by engineer. + Show calls in phone history Ukaž hovory v historii telefonu @@ -4847,6 +5087,10 @@ Error: %@ Zastavit SimpleX authentication reason + + Stop chat + No comment provided by engineer. + Stop chat to enable database actions Zastavte chat pro povolení akcí databáze @@ -4887,6 +5131,10 @@ Error: %@ Přestat sdílet adresu? No comment provided by engineer. + + Stopping chat + No comment provided by engineer. + Submit Odeslat @@ -5129,6 +5377,14 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Tuto akci nelze vzít zpět - váš profil, kontakty, zprávy a soubory budou nenávratně ztraceny. No comment provided by engineer. + + This chat is protected by end-to-end encryption. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + E2EE info chat item + This device name No comment provided by engineer. @@ -5409,11 +5665,19 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Zvýšit a otevřít chat No comment provided by engineer. + + Upload failed + No comment provided by engineer. + Upload file Nahrát soubor server test step + + Uploading archive + No comment provided by engineer. + Use .onion hosts Použít hostitele .onion @@ -5462,6 +5726,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Použít server No comment provided by engineer. + + Use the app while in the call. + No comment provided by engineer. + User profile Profil uživatele @@ -5494,6 +5762,14 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Verify connections No comment provided by engineer. + + Verify database passphrase + No comment provided by engineer. + + + Verify passphrase + No comment provided by engineer. + Verify security code Ověření bezpečnostního kódu @@ -5581,6 +5857,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Čekám na video No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + No comment provided by engineer. + Warning: you may lose some data! Upozornění: můžete ztratit nějaká data! @@ -5601,6 +5881,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Uvítací zpráva No comment provided by engineer. + + Welcome message is too long + No comment provided by engineer. + What's new Co je nového @@ -5654,6 +5938,10 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Vy No comment provided by engineer. + + You **must not** use the same database on two devices. + No comment provided by engineer. + You accepted connection Přijali jste spojení @@ -5733,6 +6021,10 @@ Repeat join request? Můžete je povolit později v nastavení Soukromí & Bezpečnosti aplikace No comment provided by engineer. + + You can give another try. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. Profil uživatele můžete skrýt nebo ztlumit - přejeďte prstem doprava. @@ -6116,7 +6408,7 @@ Servery SimpleX nevidí váš profil. blocked - No comment provided by engineer. + marked deleted chat item preview text blocked %@ @@ -6124,7 +6416,7 @@ Servery SimpleX nevidí váš profil. blocked by admin - blocked chat item + marked deleted chat item preview text bold @@ -6550,7 +6842,7 @@ Servery SimpleX nevidí váš profil. moderated by %@ moderovaný %@ - No comment provided by engineer. + marked deleted chat item preview text months @@ -6619,6 +6911,10 @@ Servery SimpleX nevidí váš profil. peer-to-peer No comment provided by engineer. + + quantum resistant e2e encryption + chat item text + received answer… obdržel odpověď… @@ -6690,6 +6986,10 @@ Servery SimpleX nevidí váš profil. set new profile picture profile update event chat item + + standard end-to-end encryption + chat item text + starting… začíná… diff --git a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff index d0f217a0e1..3ed8ecbaeb 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -107,6 +107,11 @@ %@ wurde mit Ihnen verbunden No comment provided by engineer. + + %@ downloaded + %@ heruntergeladen + No comment provided by engineer. + %@ is connected! %@ ist mit Ihnen verbunden! @@ -127,6 +132,11 @@ %@-Server No comment provided by engineer. + + %@ uploaded + %@ hochgeladen + No comment provided by engineer. + %@ wants to connect! %@ will sich mit Ihnen verbinden! @@ -342,6 +352,11 @@ **Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen). No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Bitte beachten Sie**: Aus Sicherheitsgründen wird die Nachrichtenentschlüsselung Ihrer Verbindungen abgebrochen, wenn Sie die gleiche Datenbank auf zwei Geräten nutzen. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Bitte beachten Sie**: Das Passwort kann NICHT wiederhergestellt oder geändert werden, wenn Sie es vergessen haben oder verlieren. @@ -357,6 +372,11 @@ **Warnung**: Sofortige Push-Benachrichtigungen erfordern die Eingabe eines Passworts, welches in Ihrem Schlüsselbund gespeichert ist. No comment provided by engineer. + + **Warning**: the archive will be removed. + **Warnung**: Das Archiv wird gelöscht. + No comment provided by engineer. + **e2e encrypted** audio call **E2E-verschlüsselter** Audioanruf @@ -613,6 +633,11 @@ Der Wechsel der Empfängeradresse wird abgebrochen. Die bisherige Adresse wird weiter verwendet. No comment provided by engineer. + + Admins can block a member for all. + Administratoren können für ein Mitglied alle Funktionen blockieren. + No comment provided by engineer. + Admins can create the links to join groups. Administratoren können Links für den Beitritt zu Gruppen erzeugen. @@ -668,6 +693,11 @@ Alle Ihre Kontakte bleiben verbunden. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet. No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Alle Ihre Kontakte, Unterhaltungen und Dateien werden sicher verschlüsselt und in Daten-Paketen auf die konfigurierten XTFP-Server hochgeladen. + No comment provided by engineer. + Allow Erlauben @@ -793,6 +823,11 @@ App Build: %@ No comment provided by engineer. + + App data migration + App-Daten-Migration + No comment provided by engineer. + App encrypts new local files (except videos). Neue lokale Dateien (außer Video-Dateien) werden von der App verschlüsselt. @@ -828,6 +863,21 @@ Erscheinungsbild No comment provided by engineer. + + Apply + Anwenden + No comment provided by engineer. + + + Archive and upload + Archivieren und Hochladen + No comment provided by engineer. + + + Archiving database + Datenbank wird archiviert + No comment provided by engineer. + Attach Anhängen @@ -1018,6 +1068,11 @@ Abbrechen No comment provided by engineer. + + Cancel migration + Migration abbrechen + No comment provided by engineer. + Cannot access keychain to save database password Die App kann nicht auf den Schlüsselbund zugreifen, um das Datenbank-Passwort zu speichern @@ -1119,6 +1174,11 @@ Der Chat ist angehalten. Wenn Sie diese Datenbank bereits auf einem anderen Gerät genutzt haben, sollten Sie diese vor dem Starten des Chats wieder zurückspielen. No comment provided by engineer. + + Chat migrated! + Chat wurde migriert! + No comment provided by engineer. + Chat preferences Chat-Präferenzen @@ -1139,6 +1199,11 @@ Chinesische und spanische Bedienoberfläche No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + Wählen Sie auf dem neuen Gerät _Von einem anderen Gerät migrieren_ und scannen Sie den QR-Code. + No comment provided by engineer. + Choose file Datei auswählen @@ -1209,6 +1274,11 @@ Datenbank-Aktualisierungen bestätigen No comment provided by engineer. + + Confirm network settings + Bestätigen Sie die Netzwerkeinstellungen + No comment provided by engineer. + Confirm new passphrase… Neues Passwort bestätigen… @@ -1219,6 +1289,16 @@ Passwort bestätigen No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + Für die Migration bestätigen Sie bitte, dass Sie sich an das Datenbank-Passwort erinnern. + No comment provided by engineer. + + + Confirm upload + Hochladen bestätigen + No comment provided by engineer. + Connect Verbinden @@ -1478,6 +1558,11 @@ Das ist Ihr eigener Einmal-Link! Erstellt am %@ No comment provided by engineer. + + Creating archive link + Archiv-Link erzeugen + No comment provided by engineer. + Creating link… Link wird erstellt… @@ -1698,6 +1783,11 @@ Das kann nicht rückgängig gemacht werden! Datenbank löschen No comment provided by engineer. + + Delete database from this device + Datenbank auf diesem Gerät löschen + No comment provided by engineer. + Delete file Datei löschen @@ -1988,11 +2078,26 @@ Das kann nicht rückgängig gemacht werden! Datenbank herabstufen und den Chat öffnen No comment provided by engineer. + + Download failed + Herunterladen fehlgeschlagen + No comment provided by engineer. + Download file Datei herunterladen server test step + + Downloading archive + Archiv wird heruntergeladen + No comment provided by engineer. + + + Downloading link details + Link-Details werden heruntergeladen + No comment provided by engineer. + Duplicate display name! Doppelter Anzeigename! @@ -2048,6 +2153,11 @@ Das kann nicht rückgängig gemacht werden! Für Alle aktivieren No comment provided by engineer. + + Enable in direct chats (BETA)! + Kann in direkten Chats aktiviert werden (BETA)! + No comment provided by engineer. + Enable instant notifications? Sofortige Benachrichtigungen aktivieren? @@ -2163,6 +2273,11 @@ Das kann nicht rückgängig gemacht werden! Geben Sie den Gruppennamen ein… No comment provided by engineer. + + Enter passphrase + Passwort eingeben + No comment provided by engineer. + Enter passphrase… Passwort eingeben… @@ -2223,6 +2338,11 @@ Das kann nicht rückgängig gemacht werden! Fehler beim Hinzufügen von Mitgliedern No comment provided by engineer. + + Error allowing contact PQ encryption + Fehler beim Zulassen der Kontakt-PQ-Verschlüsselung + No comment provided by engineer. + Error changing address Fehler beim Wechseln der Empfängeradresse @@ -2313,6 +2433,11 @@ Das kann nicht rückgängig gemacht werden! Fehler beim Löschen des Benutzerprofils No comment provided by engineer. + + Error downloading the archive + Fehler beim Herunterladen des Archivs + No comment provided by engineer. + Error enabling delivery receipts! Fehler beim Aktivieren von Empfangsbestätigungen! @@ -2388,6 +2513,11 @@ Das kann nicht rückgängig gemacht werden! Fehler beim Speichern des Passworts in den Schlüsselbund No comment provided by engineer. + + Error saving settings + Fehler beim Abspeichern der Einstellungen + when migrating + Error saving user password Fehler beim Speichern des Benutzer-Passworts @@ -2458,6 +2588,16 @@ Das kann nicht rückgängig gemacht werden! Fehler beim Aktualisieren der Benutzer-Privatsphäre No comment provided by engineer. + + Error uploading the archive + Fehler beim Hochladen des Archivs + No comment provided by engineer. + + + Error verifying passphrase: + Fehler bei der Überprüfung des Passworts: + No comment provided by engineer. + Error: Fehler: @@ -2508,6 +2648,11 @@ Das kann nicht rückgängig gemacht werden! Exportiertes Datenbankarchiv. No comment provided by engineer. + + Exported file doesn't exist + Die exportierte Datei ist nicht vorhanden + No comment provided by engineer. + Exporting database archive… Exportieren des Datenbank-Archivs… @@ -2578,6 +2723,16 @@ Das kann nicht rückgängig gemacht werden! Nach ungelesenen und favorisierten Chats filtern. No comment provided by engineer. + + Finalize migration + Die Migration wird abgeschlossen + No comment provided by engineer. + + + Finalize migration on another device. + Die Migration auf dem anderen Gerät wird abgeschlossen. + No comment provided by engineer. + Finally, we have them! 🚀 Endlich haben wir sie! 🚀 @@ -2868,6 +3023,11 @@ Das kann nicht rückgängig gemacht werden! Wie Sie Ihre Server nutzen No comment provided by engineer. + + Hungarian interface + Ungarische Bedienoberfläche + No comment provided by engineer. + ICE servers (one per line) ICE-Server (einer pro Zeile) @@ -2933,6 +3093,16 @@ Das kann nicht rückgängig gemacht werden! Datenbank importieren No comment provided by engineer. + + Import failed + Import ist fehlgeschlagen + No comment provided by engineer. + + + Importing archive + Archiv wird importiert + No comment provided by engineer. + Improved message delivery Verbesserte Zustellung von Nachrichten @@ -2948,6 +3118,11 @@ Das kann nicht rückgängig gemacht werden! Verbesserte Serverkonfiguration No comment provided by engineer. + + In order to continue, chat should be stopped. + Um fortzufahren, sollte der Chat beendet werden. + No comment provided by engineer. + In reply to Als Antwort auf @@ -3060,6 +3235,11 @@ Das kann nicht rückgängig gemacht werden! Ungültiger Link No comment provided by engineer. + + Invalid migration confirmation + Migrations-Bestätigung ungültig + No comment provided by engineer. + Invalid name! Ungültiger Name! @@ -3428,6 +3608,11 @@ Das ist Ihr Link für die Gruppe %@! Nachrichtentext No comment provided by engineer. + + Message too large + Die Nachricht ist zu lang + No comment provided by engineer. + Messages Nachrichten @@ -3443,11 +3628,56 @@ Das ist Ihr Link für die Gruppe %@! Die Nachrichten von %@ werden angezeigt! No comment provided by engineer. + + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt. + No comment provided by engineer. + + + Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. + Nachrichten, Dateien und Anrufe sind durch **Quantum-resistente E2E-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt. + No comment provided by engineer. + + + Migrate device + Gerät migrieren + No comment provided by engineer. + + + Migrate from another device + Von einem anderen Gerät migrieren + No comment provided by engineer. + + + Migrate here + Hierher migrieren + No comment provided by engineer. + + + Migrate to another device + Auf ein anderes Gerät migrieren + No comment provided by engineer. + + + Migrate to another device via QR code. + Über einen QR-Code auf ein anderes Gerät migrieren. + No comment provided by engineer. + + + Migrating + Migrieren + No comment provided by engineer. + Migrating database archive… Datenbank-Archiv wird migriert… No comment provided by engineer. + + Migration complete + Migration abgeschlossen + No comment provided by engineer. + Migration error: Fehler bei der Migration: @@ -3807,6 +4037,11 @@ Das ist Ihr Link für die Gruppe %@! Gruppe öffnen No comment provided by engineer. + + Open migration to another device + Migration auf ein anderes Gerät öffnen + authentication reason + Open user profiles Benutzerprofile öffnen @@ -3822,11 +4057,21 @@ Das ist Ihr Link für die Gruppe %@! App wird geöffnet… No comment provided by engineer. + + Or paste archive link + Oder fügen Sie den Archiv-Link ein + No comment provided by engineer. + Or scan QR code Oder den QR-Code scannen No comment provided by engineer. + + Or securely share this file link + Oder teilen Sie diesen Datei-Link sicher + No comment provided by engineer. + Or show this code Oder diesen QR-Code anzeigen @@ -3912,6 +4157,11 @@ Das ist Ihr Link für die Gruppe %@! Entschlüsselungsfehler message decrypt error item + + Picture-in-picture calls + Bild-in-Bild-Anrufe + No comment provided by engineer. + Please ask your contact to enable sending voice messages. Bitten Sie Ihren Kontakt darum, das Senden von Sprachnachrichten zu aktivieren. @@ -3932,6 +4182,11 @@ Das ist Ihr Link für die Gruppe %@! Bitte überprüfen sie sowohl Ihre, als auch die Präferenzen Ihres Kontakts. No comment provided by engineer. + + Please confirm that network settings are correct for this device. + Bitte bestätigen Sie, dass die Netzwerkeinstellungen auf diesem Gerät richtig sind. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3989,6 +4244,11 @@ Fehler: %@ Der Fingerabdruck des Zertifikats in der Serveradresse ist wahrscheinlich ungültig server test error + + Post-quantum E2EE + Post-Quantum E2E-Verschlüsselung + No comment provided by engineer. + Preserve the last message draft, with attachments. Den letzten Nachrichtenentwurf, auch mit seinen Anhängen, aufbewahren. @@ -4124,6 +4384,16 @@ Fehler: %@ Push-Benachrichtigungen No comment provided by engineer. + + Push server + Push-Server + No comment provided by engineer. + + + Quantum resistant encryption + Quantum-resistente Verschlüsselung + No comment provided by engineer. + Rate the app Bewerten Sie die App @@ -4309,11 +4579,26 @@ Fehler: %@ Verbindungsanfrage wiederholen? No comment provided by engineer. + + Repeat download + Herunterladen wiederholen + No comment provided by engineer. + + + Repeat import + Import wiederholen + No comment provided by engineer. + Repeat join request? Verbindungsanfrage wiederholen? No comment provided by engineer. + + Repeat upload + Hochladen wiederholen + No comment provided by engineer. + Reply Antwort @@ -4414,6 +4699,11 @@ Fehler: %@ SMP-Server No comment provided by engineer. + + Safer groups + Sicherere Gruppen + No comment provided by engineer. + Save Speichern @@ -4536,7 +4826,7 @@ Fehler: %@ Search bar accepts invitation links. - Von der Suchleiste werden Einladungslinks akzeptiert. + In der Suchleiste werden nun auch Einladungslinks akzeptiert. No comment provided by engineer. @@ -4779,6 +5069,11 @@ Fehler: %@ Zugangscode einstellen No comment provided by engineer. + + Set passphrase + Passwort festlegen + No comment provided by engineer. + Set passphrase to export Passwort für den Export festlegen @@ -4834,6 +5129,11 @@ Fehler: %@ Mit Kontakten teilen No comment provided by engineer. + + Show QR code + QR-Code anzeigen + No comment provided by engineer. + Show calls in phone history Anrufliste anzeigen @@ -4974,6 +5274,11 @@ Fehler: %@ Stoppen Sie SimpleX authentication reason + + Stop chat + Chat beenden + No comment provided by engineer. + Stop chat to enable database actions Chat beenden, um Datenbankaktionen zu erlauben @@ -5014,6 +5319,11 @@ Fehler: %@ Das Teilen der Adresse beenden? No comment provided by engineer. + + Stopping chat + Chat wird beendet + No comment provided by engineer. + Submit Bestätigen @@ -5261,6 +5571,16 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Diese Aktion kann nicht rückgängig gemacht werden! Ihr Profil und Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren. No comment provided by engineer. + + This chat is protected by end-to-end encryption. + Dieser Chat ist durch Ende-zu-Ende-Verschlüsselung geschützt. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + Dieser Chat ist durch Quantum-resistente Ende-zu-Ende-Verschlüsselung geschützt. + E2EE info chat item + This device name Dieser Gerätename @@ -5555,11 +5875,21 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Aktualisieren und den Chat öffnen No comment provided by engineer. + + Upload failed + Hochladen fehlgeschlagen + No comment provided by engineer. + Upload file Datei hochladen server test step + + Uploading archive + Archiv wird hochgeladen + No comment provided by engineer. + Use .onion hosts Verwende .onion-Hosts @@ -5610,6 +5940,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Server nutzen No comment provided by engineer. + + Use the app while in the call. + Die App kann während eines Anrufs genutzt werden. + No comment provided by engineer. + User profile Benutzerprofil @@ -5645,6 +5980,16 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Verbindungen überprüfen No comment provided by engineer. + + Verify database passphrase + Überprüfen Sie das Datenbank-Passwort + No comment provided by engineer. + + + Verify passphrase + Überprüfen Sie das Passwort + No comment provided by engineer. + Verify security code Sicherheitscode überprüfen @@ -5735,6 +6080,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Auf das Video warten No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Warnung: Das Starten des Chats auf mehreren Geräten wird nicht unterstützt und wird zu Fehlern bei der Nachrichtenübermittlung führen + No comment provided by engineer. + Warning: you may lose some data! Warnung: Sie könnten einige Daten verlieren! @@ -5755,6 +6105,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Begrüßungsmeldung No comment provided by engineer. + + Welcome message is too long + Die Begrüßungsmeldung ist zu lang + No comment provided by engineer. + What's new Was ist neu @@ -5810,6 +6165,11 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Profil No comment provided by engineer. + + You **must not** use the same database on two devices. + Sie dürfen die selbe Datenbank **nicht** auf zwei Geräten nutzen. + No comment provided by engineer. + You accepted connection Sie haben die Verbindung akzeptiert @@ -5897,6 +6257,11 @@ Verbindungsanfrage wiederholen? Sie können diese später in den Datenschutz & Sicherheits-Einstellungen der App aktivieren. No comment provided by engineer. + + You can give another try. + Sie können es nochmal probieren. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. Sie können ein Benutzerprofil verbergen oder stummschalten - wischen Sie es nach rechts. @@ -6291,7 +6656,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. blocked Blockiert - No comment provided by engineer. + marked deleted chat item preview text blocked %@ @@ -6301,7 +6666,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. blocked by admin wurde vom Administrator blockiert - blocked chat item + marked deleted chat item preview text bold @@ -6425,7 +6790,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. contact %1$@ changed to %2$@ - Der Kontaktname %1$@ wurde auf %2$@ geändert + Der Kontaktname wurde von %1$@ auf %2$@ geändert profile update event chat item @@ -6700,7 +7065,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. member %1$@ changed to %2$@ - Der Mitgliedsname %1$@ wurde auf %2$@ geändert + Der Mitgliedsname von %1$@ wurde auf %2$@ geändert profile update event chat item @@ -6731,7 +7096,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. moderated by %@ Von %@ moderiert - No comment provided by engineer. + marked deleted chat item preview text months @@ -6800,6 +7165,11 @@ SimpleX-Server können Ihr Profil nicht einsehen. Peer-to-Peer No comment provided by engineer. + + quantum resistant e2e encryption + Quantum-resistente E2E-Verschlüsselung + chat item text + received answer… Antwort erhalten… @@ -6827,12 +7197,12 @@ SimpleX-Server können Ihr Profil nicht einsehen. removed contact address - Kontaktadresse wurde entfernt + Die Kontaktadresse wurde entfernt profile update event chat item removed profile picture - Profil-Bild wurde entfernt + Das Profil-Bild wurde entfernt profile update event chat item @@ -6867,14 +7237,19 @@ SimpleX-Server können Ihr Profil nicht einsehen. set new contact address - Neue Kontaktadresse wurde festgelegt + Es wurde eine neue Kontaktadresse festgelegt profile update event chat item set new profile picture - Neues Profil-Bild wurde festgelegt + Es wurde ein neues Profil-Bild festgelegt profile update event chat item + + standard end-to-end encryption + Standard-Ende-zu-Ende-Verschlüsselung + chat item text + starting… Verbindung wird gestartet… diff --git a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff index b2b9cbce59..39ee4e2a9b 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -107,6 +107,11 @@ %@ connected No comment provided by engineer. + + %@ downloaded + %@ downloaded + No comment provided by engineer. + %@ is connected! %@ is connected! @@ -127,6 +132,11 @@ %@ servers No comment provided by engineer. + + %@ uploaded + %@ uploaded + No comment provided by engineer. + %@ wants to connect! %@ wants to connect! @@ -342,6 +352,11 @@ **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Please note**: you will NOT be able to recover or change passphrase if you lose it. @@ -357,6 +372,11 @@ **Warning**: Instant push notifications require passphrase saved in Keychain. No comment provided by engineer. + + **Warning**: the archive will be removed. + **Warning**: the archive will be removed. + No comment provided by engineer. + **e2e encrypted** audio call **e2e encrypted** audio call @@ -613,6 +633,11 @@ Address change will be aborted. Old receiving address will be used. No comment provided by engineer. + + Admins can block a member for all. + Admins can block a member for all. + No comment provided by engineer. + Admins can create the links to join groups. Admins can create the links to join groups. @@ -668,6 +693,11 @@ All your contacts will remain connected. Profile update will be sent to your contacts. No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + No comment provided by engineer. + Allow Allow @@ -793,6 +823,11 @@ App build: %@ No comment provided by engineer. + + App data migration + App data migration + No comment provided by engineer. + App encrypts new local files (except videos). App encrypts new local files (except videos). @@ -828,6 +863,21 @@ Appearance No comment provided by engineer. + + Apply + Apply + No comment provided by engineer. + + + Archive and upload + Archive and upload + No comment provided by engineer. + + + Archiving database + Archiving database + No comment provided by engineer. + Attach Attach @@ -1018,6 +1068,11 @@ Cancel No comment provided by engineer. + + Cancel migration + Cancel migration + No comment provided by engineer. + Cannot access keychain to save database password Cannot access keychain to save database password @@ -1119,6 +1174,11 @@ Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. No comment provided by engineer. + + Chat migrated! + Chat migrated! + No comment provided by engineer. + Chat preferences Chat preferences @@ -1139,6 +1199,11 @@ Chinese and Spanish interface No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + Choose _Migrate from another device_ on the new device and scan QR code. + No comment provided by engineer. + Choose file Choose file @@ -1209,6 +1274,11 @@ Confirm database upgrades No comment provided by engineer. + + Confirm network settings + Confirm network settings + No comment provided by engineer. + Confirm new passphrase… Confirm new passphrase… @@ -1219,6 +1289,16 @@ Confirm password No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + Confirm that you remember database passphrase to migrate it. + No comment provided by engineer. + + + Confirm upload + Confirm upload + No comment provided by engineer. + Connect Connect @@ -1478,6 +1558,11 @@ This is your own one-time link! Created on %@ No comment provided by engineer. + + Creating archive link + Creating archive link + No comment provided by engineer. + Creating link… Creating link… @@ -1698,6 +1783,11 @@ This cannot be undone! Delete database No comment provided by engineer. + + Delete database from this device + Delete database from this device + No comment provided by engineer. + Delete file Delete file @@ -1988,11 +2078,26 @@ This cannot be undone! Downgrade and open chat No comment provided by engineer. + + Download failed + Download failed + No comment provided by engineer. + Download file Download file server test step + + Downloading archive + Downloading archive + No comment provided by engineer. + + + Downloading link details + Downloading link details + No comment provided by engineer. + Duplicate display name! Duplicate display name! @@ -2048,6 +2153,11 @@ This cannot be undone! Enable for all No comment provided by engineer. + + Enable in direct chats (BETA)! + Enable in direct chats (BETA)! + No comment provided by engineer. + Enable instant notifications? Enable instant notifications? @@ -2163,6 +2273,11 @@ This cannot be undone! Enter group name… No comment provided by engineer. + + Enter passphrase + Enter passphrase + No comment provided by engineer. + Enter passphrase… Enter passphrase… @@ -2223,6 +2338,11 @@ This cannot be undone! Error adding member(s) No comment provided by engineer. + + Error allowing contact PQ encryption + Error allowing contact PQ encryption + No comment provided by engineer. + Error changing address Error changing address @@ -2313,6 +2433,11 @@ This cannot be undone! Error deleting user profile No comment provided by engineer. + + Error downloading the archive + Error downloading the archive + No comment provided by engineer. + Error enabling delivery receipts! Error enabling delivery receipts! @@ -2388,6 +2513,11 @@ This cannot be undone! Error saving passphrase to keychain No comment provided by engineer. + + Error saving settings + Error saving settings + when migrating + Error saving user password Error saving user password @@ -2458,6 +2588,16 @@ This cannot be undone! Error updating user privacy No comment provided by engineer. + + Error uploading the archive + Error uploading the archive + No comment provided by engineer. + + + Error verifying passphrase: + Error verifying passphrase: + No comment provided by engineer. + Error: Error: @@ -2508,6 +2648,11 @@ This cannot be undone! Exported database archive. No comment provided by engineer. + + Exported file doesn't exist + Exported file doesn't exist + No comment provided by engineer. + Exporting database archive… Exporting database archive… @@ -2578,6 +2723,16 @@ This cannot be undone! Filter unread and favorite chats. No comment provided by engineer. + + Finalize migration + Finalize migration + No comment provided by engineer. + + + Finalize migration on another device. + Finalize migration on another device. + No comment provided by engineer. + Finally, we have them! 🚀 Finally, we have them! 🚀 @@ -2868,6 +3023,11 @@ This cannot be undone! How to use your servers No comment provided by engineer. + + Hungarian interface + Hungarian interface + No comment provided by engineer. + ICE servers (one per line) ICE servers (one per line) @@ -2933,6 +3093,16 @@ This cannot be undone! Import database No comment provided by engineer. + + Import failed + Import failed + No comment provided by engineer. + + + Importing archive + Importing archive + No comment provided by engineer. + Improved message delivery Improved message delivery @@ -2948,6 +3118,11 @@ This cannot be undone! Improved server configuration No comment provided by engineer. + + In order to continue, chat should be stopped. + In order to continue, chat should be stopped. + No comment provided by engineer. + In reply to In reply to @@ -3060,6 +3235,11 @@ This cannot be undone! Invalid link No comment provided by engineer. + + Invalid migration confirmation + Invalid migration confirmation + No comment provided by engineer. + Invalid name! Invalid name! @@ -3428,6 +3608,11 @@ This is your link for group %@! Message text No comment provided by engineer. + + Message too large + Message too large + No comment provided by engineer. + Messages Messages @@ -3443,11 +3628,56 @@ This is your link for group %@! Messages from %@ will be shown! 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. + 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. + Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. + No comment provided by engineer. + + + Migrate device + Migrate device + No comment provided by engineer. + + + Migrate from another device + Migrate from another device + No comment provided by engineer. + + + Migrate here + Migrate here + No comment provided by engineer. + + + Migrate to another device + Migrate to another device + No comment provided by engineer. + + + Migrate to another device via QR code. + Migrate to another device via QR code. + No comment provided by engineer. + + + Migrating + Migrating + No comment provided by engineer. + Migrating database archive… Migrating database archive… No comment provided by engineer. + + Migration complete + Migration complete + No comment provided by engineer. + Migration error: Migration error: @@ -3807,6 +4037,11 @@ This is your link for group %@! Open group No comment provided by engineer. + + Open migration to another device + Open migration to another device + authentication reason + Open user profiles Open user profiles @@ -3822,11 +4057,21 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or paste archive link + Or paste archive link + No comment provided by engineer. + Or scan QR code Or scan QR code No comment provided by engineer. + + Or securely share this file link + Or securely share this file link + No comment provided by engineer. + Or show this code Or show this code @@ -3912,6 +4157,11 @@ This is your link for group %@! Permanent decryption error message decrypt error item + + Picture-in-picture calls + Picture-in-picture calls + No comment provided by engineer. + Please ask your contact to enable sending voice messages. Please ask your contact to enable sending voice messages. @@ -3932,6 +4182,11 @@ This is your link for group %@! Please check yours and your contact preferences. No comment provided by engineer. + + Please confirm that network settings are correct for this device. + Please confirm that network settings are correct for this device. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3989,6 +4244,11 @@ Error: %@ Possibly, certificate fingerprint in server address is incorrect server test error + + Post-quantum E2EE + Post-quantum E2EE + No comment provided by engineer. + Preserve the last message draft, with attachments. Preserve the last message draft, with attachments. @@ -4124,6 +4384,16 @@ Error: %@ Push notifications No comment provided by engineer. + + Push server + Push server + No comment provided by engineer. + + + Quantum resistant encryption + Quantum resistant encryption + No comment provided by engineer. + Rate the app Rate the app @@ -4309,11 +4579,26 @@ Error: %@ Repeat connection request? No comment provided by engineer. + + Repeat download + Repeat download + No comment provided by engineer. + + + Repeat import + Repeat import + No comment provided by engineer. + Repeat join request? Repeat join request? No comment provided by engineer. + + Repeat upload + Repeat upload + No comment provided by engineer. + Reply Reply @@ -4414,6 +4699,11 @@ Error: %@ SMP servers No comment provided by engineer. + + Safer groups + Safer groups + No comment provided by engineer. + Save Save @@ -4779,6 +5069,11 @@ Error: %@ Set passcode No comment provided by engineer. + + Set passphrase + Set passphrase + No comment provided by engineer. + Set passphrase to export Set passphrase to export @@ -4834,6 +5129,11 @@ Error: %@ Share with contacts No comment provided by engineer. + + Show QR code + Show QR code + No comment provided by engineer. + Show calls in phone history Show calls in phone history @@ -4974,6 +5274,11 @@ Error: %@ Stop SimpleX authentication reason + + Stop chat + Stop chat + No comment provided by engineer. + Stop chat to enable database actions Stop chat to enable database actions @@ -5014,6 +5319,11 @@ Error: %@ Stop sharing address? No comment provided by engineer. + + Stopping chat + Stopping chat + No comment provided by engineer. + Submit Submit @@ -5261,6 +5571,16 @@ It can happen because of some bug or when the connection is compromised.This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. No comment provided by engineer. + + This chat is protected by end-to-end encryption. + This chat is protected by end-to-end encryption. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + This chat is protected by quantum resistant end-to-end encryption. + E2EE info chat item + This device name This device name @@ -5555,11 +5875,21 @@ To connect, please ask your contact to create another connection link and check Upgrade and open chat No comment provided by engineer. + + Upload failed + Upload failed + No comment provided by engineer. + Upload file Upload file server test step + + Uploading archive + Uploading archive + No comment provided by engineer. + Use .onion hosts Use .onion hosts @@ -5610,6 +5940,11 @@ To connect, please ask your contact to create another connection link and check Use server No comment provided by engineer. + + Use the app while in the call. + Use the app while in the call. + No comment provided by engineer. + User profile User profile @@ -5645,6 +5980,16 @@ To connect, please ask your contact to create another connection link and check Verify connections No comment provided by engineer. + + Verify database passphrase + Verify database passphrase + No comment provided by engineer. + + + Verify passphrase + Verify passphrase + No comment provided by engineer. + Verify security code Verify security code @@ -5735,6 +6080,11 @@ To connect, please ask your contact to create another connection link and check Waiting for video No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + No comment provided by engineer. + Warning: you may lose some data! Warning: you may lose some data! @@ -5755,6 +6105,11 @@ To connect, please ask your contact to create another connection link and check Welcome message No comment provided by engineer. + + Welcome message is too long + Welcome message is too long + No comment provided by engineer. + What's new What's new @@ -5810,6 +6165,11 @@ To connect, please ask your contact to create another connection link and check You No comment provided by engineer. + + You **must not** use the same database on two devices. + You **must not** use the same database on two devices. + No comment provided by engineer. + You accepted connection You accepted connection @@ -5897,6 +6257,11 @@ Repeat join request? You can enable them later via app Privacy & Security settings. No comment provided by engineer. + + You can give another try. + You can give another try. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. You can hide or mute a user profile - swipe it to the right. @@ -6291,7 +6656,7 @@ SimpleX servers cannot see your profile. blocked blocked - No comment provided by engineer. + marked deleted chat item preview text blocked %@ @@ -6301,7 +6666,7 @@ SimpleX servers cannot see your profile. blocked by admin blocked by admin - blocked chat item + marked deleted chat item preview text bold @@ -6731,7 +7096,7 @@ SimpleX servers cannot see your profile. moderated by %@ moderated by %@ - No comment provided by engineer. + marked deleted chat item preview text months @@ -6800,6 +7165,11 @@ SimpleX servers cannot see your profile. peer-to-peer No comment provided by engineer. + + quantum resistant e2e encryption + quantum resistant e2e encryption + chat item text + received answer… received answer… @@ -6875,6 +7245,11 @@ SimpleX servers cannot see your profile. set new profile picture profile update event chat item + + standard end-to-end encryption + standard end-to-end encryption + chat item text + starting… starting… diff --git a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff index bed077aa46..2f279a963c 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -107,6 +107,11 @@ %@ conectado No comment provided by engineer. + + %@ downloaded + %@ downloaded + No comment provided by engineer. + %@ is connected! %@ ¡está conectado! @@ -127,6 +132,10 @@ Servidores %@ No comment provided by engineer. + + %@ uploaded + No comment provided by engineer. + %@ wants to connect! ¡ %@ quiere contactar! @@ -219,6 +228,7 @@ %lld messages blocked by admin + %lld mensajes bloqueados por el administrador No comment provided by engineer. @@ -341,6 +351,11 @@ **Más privado**: no se usa el servidor de notificaciones de SimpleX Chat, los mensajes se comprueban periódicamente en segundo plano (dependiendo de la frecuencia con la que utilices la aplicación). No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Tenga en cuenta**: usar la misma base de datos en dos dispositivos interrumpirá el descifrado de mensajes de sus conexiones, como protección de seguridad. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Atención**: NO podrás recuperar o cambiar la contraseña si la pierdes. @@ -356,6 +371,11 @@ **Advertencia**: Las notificaciones automáticas instantáneas requieren una contraseña guardada en Keychain. No comment provided by engineer. + + **Warning**: the archive will be removed. + **Atención**: el archivo será eliminado. + No comment provided by engineer. + **e2e encrypted** audio call Llamada con **cifrado de extremo a extremo ** @@ -612,6 +632,11 @@ El cambio de dirección se cancelará. Se usará la antigua dirección de recepción. No comment provided by engineer. + + Admins can block a member for all. + Los admins pueden bloquear un miembro por todos. + No comment provided by engineer. + Admins can create the links to join groups. Los administradores pueden crear enlaces para unirse a grupos. @@ -644,6 +669,7 @@ All messages will be deleted - this cannot be undone! + Todos los mensajes serán borrados. ¡No podrá deshacerse! No comment provided by engineer. @@ -666,6 +692,11 @@ Todos tus contactos permanecerán conectados. La actualización del perfil se enviará a tus contactos. No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Todos sus contactos, conversaciones y archivos se cifrarán de forma segura y se subirán en fragmentos hacia puntos XFTP configurados. + No comment provided by engineer. + Allow Se permite @@ -791,6 +822,11 @@ Compilación app: %@ No comment provided by engineer. + + App data migration + Migración de datos de la aplicación + No comment provided by engineer. + App encrypts new local files (except videos). Cifrado de los nuevos archivos locales (excepto vídeos). @@ -826,6 +862,21 @@ Apariencia No comment provided by engineer. + + Apply + Aplicar + No comment provided by engineer. + + + Archive and upload + Archivar y transferir + No comment provided by engineer. + + + Archiving database + Archivando base de datos + No comment provided by engineer. + Attach Adjuntar @@ -923,6 +974,7 @@ Block for all + Bloquear para todos No comment provided by engineer. @@ -937,6 +989,7 @@ Block member for all? + ¿Bloqear miembro para todos? No comment provided by engineer. @@ -946,6 +999,7 @@ Blocked by admin + Bloqueado por el administrador No comment provided by engineer. @@ -1013,6 +1067,11 @@ Cancelar No comment provided by engineer. + + Cancel migration + Cancelar migración + No comment provided by engineer. + Cannot access keychain to save database password Keychain inaccesible para guardar la contraseña de la base de datos @@ -1114,6 +1173,11 @@ Chat está detenido. Si estás usando esta base de datos en otro dispositivo, deberías transferirla de vuelta antes de iniciarlo. No comment provided by engineer. + + Chat migrated! + Chat transferido ! + No comment provided by engineer. + Chat preferences Preferencias de Chat @@ -1134,6 +1198,11 @@ Interfaz en chino y español No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + Elija _Migrar desde otro dispositivo_ en el nuevo dispositivo y escanee el código QR. + No comment provided by engineer. + Choose file Elije archivo @@ -1161,6 +1230,7 @@ Clear private notes? + ¿Borrar notas privadas? No comment provided by engineer. @@ -1203,6 +1273,11 @@ Confirmar actualizaciones de la bases de datos No comment provided by engineer. + + Confirm network settings + Confirmar los ajustes de red + No comment provided by engineer. + Confirm new passphrase… Confirme nueva contraseña… @@ -1213,6 +1288,15 @@ Confirmar contraseña No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + Confirme que recuerda la frase secreta de la base de datos para migrarla. + No comment provided by engineer. + + + Confirm upload + No comment provided by engineer. + Connect Conectar @@ -1459,10 +1543,12 @@ This is your own one-time link! Created at + Creado No comment provided by engineer. Created at: %@ + Creado: %@ copied message info @@ -1470,6 +1556,10 @@ This is your own one-time link! Creado en %@ No comment provided by engineer. + + Creating archive link + No comment provided by engineer. + Creating link… Creando enlace… @@ -1690,6 +1780,10 @@ This cannot be undone! Eliminar base de datos No comment provided by engineer. + + Delete database from this device + No comment provided by engineer. + Delete file Eliminar archivo @@ -1957,6 +2051,7 @@ This cannot be undone! Do not send history to new members. + No enviar historial a miembros nuevos. No comment provided by engineer. @@ -1979,11 +2074,23 @@ This cannot be undone! Degradar y abrir Chat No comment provided by engineer. + + Download failed + No comment provided by engineer. + Download file Descargar archivo server test step + + Downloading archive + No comment provided by engineer. + + + Downloading link details + No comment provided by engineer. + Duplicate display name! ¡Nombre mostrado duplicado! @@ -2039,6 +2146,10 @@ This cannot be undone! Activar para todos No comment provided by engineer. + + Enable in direct chats (BETA)! + No comment provided by engineer. + Enable instant notifications? ¿Activar notificación instantánea? @@ -2154,6 +2265,10 @@ This cannot be undone! Nombre del grupo… No comment provided by engineer. + + Enter passphrase + No comment provided by engineer. + Enter passphrase… Introduce la contraseña… @@ -2214,6 +2329,10 @@ This cannot be undone! Error al añadir miembro(s) No comment provided by engineer. + + Error allowing contact PQ encryption + No comment provided by engineer. + Error changing address Error al cambiar servidor @@ -2251,6 +2370,7 @@ This cannot be undone! Error creating message + Error al crear mensaje No comment provided by engineer. @@ -2303,6 +2423,10 @@ This cannot be undone! Error al eliminar perfil No comment provided by engineer. + + Error downloading the archive + No comment provided by engineer. + Error enabling delivery receipts! ¡Error al activar confirmaciones de entrega! @@ -2378,6 +2502,10 @@ This cannot be undone! Error al guardar contraseña en Keychain No comment provided by engineer. + + Error saving settings + when migrating + Error saving user password Error al guardar contraseña de usuario @@ -2448,6 +2576,14 @@ This cannot be undone! Error al actualizar privacidad de usuario No comment provided by engineer. + + Error uploading the archive + No comment provided by engineer. + + + Error verifying passphrase: + No comment provided by engineer. + Error: Error: @@ -2498,6 +2634,10 @@ This cannot be undone! Archivo de base de datos exportado. No comment provided by engineer. + + Exported file doesn't exist + No comment provided by engineer. + Exporting database archive… Exportando base de datos… @@ -2568,6 +2708,14 @@ This cannot be undone! Filtra chats no leídos y favoritos. No comment provided by engineer. + + Finalize migration + No comment provided by engineer. + + + Finalize migration on another device. + No comment provided by engineer. + Finally, we have them! 🚀 ¡Por fin los tenemos! 🚀 @@ -2830,6 +2978,7 @@ This cannot be undone! History is not sent to new members. + El historial no se envía a miembros nuevos. No comment provided by engineer. @@ -2857,6 +3006,10 @@ This cannot be undone! Cómo usar los servidores No comment provided by engineer. + + Hungarian interface + No comment provided by engineer. + ICE servers (one per line) Servidores ICE (uno por línea) @@ -2922,8 +3075,17 @@ This cannot be undone! Importar base de datos No comment provided by engineer. + + Import failed + No comment provided by engineer. + + + Importing archive + No comment provided by engineer. + Improved message delivery + Entrega de mensajes mejorada No comment provided by engineer. @@ -2936,6 +3098,10 @@ This cannot be undone! Configuración del servidor mejorada No comment provided by engineer. + + In order to continue, chat should be stopped. + No comment provided by engineer. + In reply to En respuesta a @@ -3040,6 +3206,7 @@ This cannot be undone! Invalid display name! + ¡Nombre mostrado no válido! No comment provided by engineer. @@ -3047,6 +3214,10 @@ This cannot be undone! Enlace no válido No comment provided by engineer. + + Invalid migration confirmation + No comment provided by engineer. + Invalid name! ¡Nombre no válido! @@ -3150,6 +3321,7 @@ This cannot be undone! Join group conversations + Unirse a la conversación del grupo No comment provided by engineer. @@ -3414,6 +3586,10 @@ This is your link for group %@! Contacto y texto No comment provided by engineer. + + Message too large + No comment provided by engineer. + Messages Mensajes @@ -3429,11 +3605,47 @@ This is your link for group %@! ¡Los mensajes de %@ serán mostrados! 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. + No comment provided by engineer. + + + Migrate device + No comment provided by engineer. + + + Migrate from another device + No comment provided by engineer. + + + Migrate here + No comment provided by engineer. + + + Migrate to another device + No comment provided by engineer. + + + Migrate to another device via QR code. + No comment provided by engineer. + + + Migrating + No comment provided by engineer. + Migrating database archive… Migrando base de datos… No comment provided by engineer. + + Migration complete + No comment provided by engineer. + Migration error: Error de migración: @@ -3793,6 +4005,10 @@ This is your link for group %@! Grupo abierto No comment provided by engineer. + + Open migration to another device + authentication reason + Open user profiles Abrir perfil de usuario @@ -3808,11 +4024,19 @@ This is your link for group %@! Iniciando aplicación… No comment provided by engineer. + + Or paste archive link + No comment provided by engineer. + Or scan QR code O escanear código QR No comment provided by engineer. + + Or securely share this file link + No comment provided by engineer. + Or show this code O mostrar este código @@ -3860,6 +4084,7 @@ This is your link for group %@! Past member %@ + Miembro pasado %@ past/unknown group member @@ -3874,6 +4099,7 @@ This is your link for group %@! Paste link to connect! + Pegar enlace para conectar! No comment provided by engineer. @@ -3896,6 +4122,10 @@ This is your link for group %@! Error permanente descifrado message decrypt error item + + Picture-in-picture calls + No comment provided by engineer. + Please ask your contact to enable sending voice messages. Solicita que tu contacto habilite el envío de mensajes de voz. @@ -3916,6 +4146,10 @@ This is your link for group %@! Comprueba tus preferencias y las de tu contacto. No comment provided by engineer. + + Please confirm that network settings are correct for this device. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3973,6 +4207,10 @@ Error: %@ Posiblemente la huella digital del certificado en la dirección del servidor es incorrecta server test error + + Post-quantum E2EE + No comment provided by engineer. + Preserve the last message draft, with attachments. Conserva el último borrador del mensaje con los datos adjuntos. @@ -4010,6 +4248,7 @@ Error: %@ Private notes + Notas privadas name of notes to self @@ -4107,6 +4346,14 @@ Error: %@ Notificaciones automáticas No comment provided by engineer. + + Push server + No comment provided by engineer. + + + Quantum resistant encryption + No comment provided by engineer. + Rate the app Valora la aplicación @@ -4194,6 +4441,7 @@ Error: %@ Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). + Historial reciente y [bot del directorio](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) mejorados. No comment provided by engineer. @@ -4291,11 +4539,23 @@ Error: %@ ¿Repetir solicitud de conexión? No comment provided by engineer. + + Repeat download + No comment provided by engineer. + + + Repeat import + No comment provided by engineer. + Repeat join request? ¿Repetir solicitud de admisión? No comment provided by engineer. + + Repeat upload + No comment provided by engineer. + Reply Responder @@ -4396,6 +4656,10 @@ Error: %@ Servidores SMP No comment provided by engineer. + + Safer groups + No comment provided by engineer. + Save Guardar @@ -4483,6 +4747,7 @@ Error: %@ Saved message + Mensaje guardado message info title @@ -4517,6 +4782,7 @@ Error: %@ Search bar accepts invitation links. + La barra de búsqueda acepta enlaces de invitación. No comment provided by engineer. @@ -4631,6 +4897,7 @@ Error: %@ Send up to 100 last messages to new members. + Enviar hasta 100 últimos mensajes a los miembros nuevos. No comment provided by engineer. @@ -4758,6 +5025,10 @@ Error: %@ Código autodestrucción No comment provided by engineer. + + Set passphrase + No comment provided by engineer. + Set passphrase to export Escribe la contraseña para exportar @@ -4813,6 +5084,10 @@ Error: %@ Compartir con contactos No comment provided by engineer. + + Show QR code + No comment provided by engineer. + Show calls in phone history Mostrar llamadas en el historial del teléfono @@ -4953,6 +5228,10 @@ Error: %@ Detener SimpleX authentication reason + + Stop chat + No comment provided by engineer. + Stop chat to enable database actions Detén SimpleX para habilitar las acciones sobre la base de datos @@ -4993,6 +5272,10 @@ Error: %@ ¿Dejar de compartir la dirección? No comment provided by engineer. + + Stopping chat + No comment provided by engineer. + Submit Enviar @@ -5227,19 +5510,27 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain. - Esta acción no se puede deshacer. Se eliminarán todos los archivos y multimedia recibidos y enviados. Las imágenes de baja resolución permanecerán. + Esta acción es irreversible. Se eliminarán todos los archivos y multimedia recibidos y enviados. Las imágenes de baja resolución permanecerán. No comment provided by engineer. This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes. - Esta acción no se puede deshacer. Se eliminarán los mensajes enviados y recibidos anteriores a la selección. Puede tardar varios minutos. + Esta acción es irreversible. Se eliminarán los mensajes enviados y recibidos anteriores a la selección. Puede tardar varios minutos. No comment provided by engineer. This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. - Esta acción no se puede deshacer. Tu perfil, contactos, mensajes y archivos se perderán irreversiblemente. + Esta acción es irreversible. Tu perfil, contactos, mensajes y archivos se perderán irreversiblemente. No comment provided by engineer. + + This chat is protected by end-to-end encryption. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + E2EE info chat item + This device name Nombre del dispositivo @@ -5247,6 +5538,7 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. This display name is invalid. Please choose another name. + Éste nombre mostrado no es válido. Por favor, elije otro nombre. No comment provided by engineer. @@ -5353,6 +5645,7 @@ Se te pedirá que completes la autenticación antes de activar esta función. Turkish interface + Interfaz en turco No comment provided by engineer. @@ -5377,6 +5670,7 @@ Se te pedirá que completes la autenticación antes de activar esta función. Unblock for all + Desbloquear para todos No comment provided by engineer. @@ -5386,6 +5680,7 @@ Se te pedirá que completes la autenticación antes de activar esta función. Unblock member for all? + ¿Desbloquear miembro para todos? No comment provided by engineer. @@ -5488,6 +5783,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Up to 100 last messages are sent to new members. + Hasta 100 últimos mensajes son enviados a los miembros nuevos. No comment provided by engineer. @@ -5530,11 +5826,19 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Actualizar y abrir Chat No comment provided by engineer. + + Upload failed + No comment provided by engineer. + Upload file Subir archivo server test step + + Uploading archive + No comment provided by engineer. + Use .onion hosts Usar hosts .onion @@ -5585,6 +5889,10 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Usar servidor No comment provided by engineer. + + Use the app while in the call. + No comment provided by engineer. + User profile Perfil de usuario @@ -5620,6 +5928,14 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Verificar conexiones No comment provided by engineer. + + Verify database passphrase + No comment provided by engineer. + + + Verify passphrase + No comment provided by engineer. + Verify security code Comprobar código de seguridad @@ -5662,6 +5978,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Visible history + Historial visible chat feature @@ -5709,6 +6026,10 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Esperando el vídeo No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + No comment provided by engineer. + Warning: you may lose some data! Atención: ¡puedes perder algunos datos! @@ -5729,6 +6050,10 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Mensaje de bienvenida No comment provided by engineer. + + Welcome message is too long + No comment provided by engineer. + What's new Novedades @@ -5751,6 +6076,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb With encrypted files and media. + Con cifrado de archivos y multimedia. No comment provided by engineer. @@ -5760,6 +6086,7 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb With reduced battery usage. + Con uso reducido de batería. No comment provided by engineer. @@ -5782,6 +6109,10 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb No comment provided by engineer. + + You **must not** use the same database on two devices. + No comment provided by engineer. + You accepted connection Has aceptado la conexión @@ -5869,6 +6200,10 @@ Repeat join request? Puedes activarlos más tarde en la configuración de Privacidad y Seguridad. No comment provided by engineer. + + You can give another try. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. Puedes ocultar o silenciar un perfil deslizándolo a la derecha. @@ -6263,15 +6598,17 @@ Los servidores de SimpleX no pueden ver tu perfil. blocked bloqueado - No comment provided by engineer. + marked deleted chat item preview text blocked %@ + %@ bloqueado rcv group event chat item blocked by admin - blocked chat item + bloqueado por el administrador + marked deleted chat item preview text bold @@ -6395,6 +6732,7 @@ Los servidores de SimpleX no pueden ver tu perfil. contact %1$@ changed to %2$@ + el contacto %1$@ ha cambiado a %2$@ profile update event chat item @@ -6669,6 +7007,7 @@ Los servidores de SimpleX no pueden ver tu perfil. member %1$@ changed to %2$@ + el miembro %1$@ ha cambiado a %2$@ profile update event chat item @@ -6699,7 +7038,7 @@ Los servidores de SimpleX no pueden ver tu perfil. moderated by %@ moderado por %@ - No comment provided by engineer. + marked deleted chat item preview text months @@ -6768,6 +7107,10 @@ Los servidores de SimpleX no pueden ver tu perfil. p2p No comment provided by engineer. + + quantum resistant e2e encryption + chat item text + received answer… respuesta recibida… @@ -6795,10 +7138,12 @@ Los servidores de SimpleX no pueden ver tu perfil. removed contact address + dirección de contacto eliminada profile update event chat item removed profile picture + imagen de perfil eliminada profile update event chat item @@ -6833,12 +7178,18 @@ Los servidores de SimpleX no pueden ver tu perfil. set new contact address + nueva dirección de contacto profile update event chat item set new profile picture + nueva imagen de perfil profile update event chat item + + standard end-to-end encryption + chat item text + starting… inicializando… @@ -6856,6 +7207,7 @@ Los servidores de SimpleX no pueden ver tu perfil. unblocked %@ + %@ desbloqueado rcv group event chat item @@ -6865,6 +7217,7 @@ Los servidores de SimpleX no pueden ver tu perfil. unknown status + estado desconocido No comment provided by engineer. @@ -6874,6 +7227,7 @@ Los servidores de SimpleX no pueden ver tu perfil. updated profile + perfil actualizado profile update event chat item @@ -6948,6 +7302,7 @@ Los servidores de SimpleX no pueden ver tu perfil. you blocked %@ + has bloqueado a %@ snd group event chat item @@ -6992,6 +7347,7 @@ Los servidores de SimpleX no pueden ver tu perfil. you unblocked %@ + has desbloqueado a %@ snd group event chat item diff --git a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff index 91dd46fd09..3e971985ef 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -105,6 +105,10 @@ %@ connected No comment provided by engineer. + + %@ downloaded + No comment provided by engineer. + %@ is connected! %@ on yhdistetty! @@ -125,6 +129,10 @@ %@ palvelimet No comment provided by engineer. + + %@ uploaded + No comment provided by engineer. + %@ wants to connect! %@ haluaa muodostaa yhteyden! @@ -330,6 +338,10 @@ **Yksityisin**: älä käytä SimpleX Chat -ilmoituspalvelinta, tarkista viestit ajoittain taustalla (riippuu siitä, kuinka usein käytät sovellusta). No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Huomaa**: et voi palauttaa tai muuttaa tunnuslausetta, jos kadotat sen. @@ -345,6 +357,10 @@ **Varoitus**: Välittömät push-ilmoitukset vaativat tunnuslauseen, joka on tallennettu Keychainiin. No comment provided by engineer. + + **Warning**: the archive will be removed. + No comment provided by engineer. + **e2e encrypted** audio call **e2e-salattu** äänipuhelu @@ -593,6 +609,10 @@ Osoitteenmuutos keskeytetään. Käytetään vanhaa vastaanotto-osoitetta. No comment provided by engineer. + + Admins can block a member for all. + No comment provided by engineer. + Admins can create the links to join groups. Ylläpitäjät voivat luoda linkkejä ryhmiin liittymiseen. @@ -646,6 +666,10 @@ Kaikki kontaktisi pysyvät yhteydessä. Profiilipäivitys lähetetään kontakteillesi. No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + No comment provided by engineer. + Allow Salli @@ -769,6 +793,10 @@ Sovellusversio: %@ No comment provided by engineer. + + App data migration + No comment provided by engineer. + App encrypts new local files (except videos). No comment provided by engineer. @@ -803,6 +831,18 @@ Ulkonäkö No comment provided by engineer. + + Apply + No comment provided by engineer. + + + Archive and upload + No comment provided by engineer. + + + Archiving database + No comment provided by engineer. + Attach Liitä @@ -982,6 +1022,10 @@ Peruuta No comment provided by engineer. + + Cancel migration + No comment provided by engineer. + Cannot access keychain to save database password Ei pääsyä avainnippuun tietokannan salasanan tallentamiseksi @@ -1082,6 +1126,10 @@ Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. No comment provided by engineer. + + Chat migrated! + No comment provided by engineer. + Chat preferences Chat-asetukset @@ -1102,6 +1150,10 @@ Kiinalainen ja espanjalainen käyttöliittymä No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + No comment provided by engineer. + Choose file Valitse tiedosto @@ -1171,6 +1223,10 @@ Vahvista tietokannan päivitykset No comment provided by engineer. + + Confirm network settings + No comment provided by engineer. + Confirm new passphrase… Vahvista uusi tunnuslause… @@ -1181,6 +1237,14 @@ Vahvista salasana No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + No comment provided by engineer. + + + Confirm upload + No comment provided by engineer. + Connect Yhdistä @@ -1421,6 +1485,10 @@ This is your own one-time link! Luotu %@ No comment provided by engineer. + + Creating archive link + No comment provided by engineer. + Creating link… No comment provided by engineer. @@ -1636,6 +1704,10 @@ This cannot be undone! Poista tietokanta No comment provided by engineer. + + Delete database from this device + No comment provided by engineer. + Delete file Poista tiedosto @@ -1920,11 +1992,23 @@ This cannot be undone! Alenna ja avaa keskustelu No comment provided by engineer. + + Download failed + No comment provided by engineer. + Download file Lataa tiedosto server test step + + Downloading archive + No comment provided by engineer. + + + Downloading link details + No comment provided by engineer. + Duplicate display name! Päällekkäinen näyttönimi! @@ -1979,6 +2063,10 @@ This cannot be undone! Salli kaikille No comment provided by engineer. + + Enable in direct chats (BETA)! + No comment provided by engineer. + Enable instant notifications? Salli välittömät ilmoitukset? @@ -2089,6 +2177,10 @@ This cannot be undone! Enter group name… No comment provided by engineer. + + Enter passphrase + No comment provided by engineer. + Enter passphrase… Syötä tunnuslause… @@ -2147,6 +2239,10 @@ This cannot be undone! Virhe lisättäessä jäseniä No comment provided by engineer. + + Error allowing contact PQ encryption + No comment provided by engineer. + Error changing address Virhe osoitteenvaihdossa @@ -2235,6 +2331,10 @@ This cannot be undone! Virhe käyttäjäprofiilin poistamisessa No comment provided by engineer. + + Error downloading the archive + No comment provided by engineer. + Error enabling delivery receipts! Virhe toimituskuittauksien sallimisessa! @@ -2309,6 +2409,10 @@ This cannot be undone! Virhe tunnuslauseen tallentamisessa avainnippuun No comment provided by engineer. + + Error saving settings + when migrating + Error saving user password Virhe käyttäjän salasanan tallentamisessa @@ -2377,6 +2481,14 @@ This cannot be undone! Virhe päivitettäessä käyttäjän tietosuojaa No comment provided by engineer. + + Error uploading the archive + No comment provided by engineer. + + + Error verifying passphrase: + No comment provided by engineer. + Error: Virhe: @@ -2426,6 +2538,10 @@ This cannot be undone! Viety tietokanta-arkisto. No comment provided by engineer. + + Exported file doesn't exist + No comment provided by engineer. + Exporting database archive… Tietokanta-arkiston vienti… @@ -2495,6 +2611,14 @@ This cannot be undone! Suodata lukemattomia- ja suosikkikeskusteluja. No comment provided by engineer. + + Finalize migration + No comment provided by engineer. + + + Finalize migration on another device. + No comment provided by engineer. + Finally, we have them! 🚀 Vihdoinkin meillä! 🚀 @@ -2780,6 +2904,10 @@ This cannot be undone! Miten käytät palvelimiasi No comment provided by engineer. + + Hungarian interface + No comment provided by engineer. + ICE servers (one per line) ICE-palvelimet (yksi per rivi) @@ -2845,6 +2973,14 @@ This cannot be undone! Tuo tietokanta No comment provided by engineer. + + Import failed + No comment provided by engineer. + + + Importing archive + No comment provided by engineer. + Improved message delivery No comment provided by engineer. @@ -2859,6 +2995,10 @@ This cannot be undone! Parannettu palvelimen kokoonpano No comment provided by engineer. + + In order to continue, chat should be stopped. + No comment provided by engineer. + In reply to Vastauksena @@ -2966,6 +3106,10 @@ This cannot be undone! Invalid link No comment provided by engineer. + + Invalid migration confirmation + No comment provided by engineer. + Invalid name! No comment provided by engineer. @@ -3321,6 +3465,10 @@ This is your link for group %@! Viestin teksti No comment provided by engineer. + + Message too large + No comment provided by engineer. + Messages Viestit @@ -3335,11 +3483,47 @@ This is your link for group %@! Messages from %@ will be shown! 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. + No comment provided by engineer. + + + Migrate device + No comment provided by engineer. + + + Migrate from another device + No comment provided by engineer. + + + Migrate here + No comment provided by engineer. + + + Migrate to another device + No comment provided by engineer. + + + Migrate to another device via QR code. + No comment provided by engineer. + + + Migrating + No comment provided by engineer. + Migrating database archive… Siirretään tietokannan arkistoa… No comment provided by engineer. + + Migration complete + No comment provided by engineer. + Migration error: Siirtovirhe: @@ -3693,6 +3877,10 @@ This is your link for group %@! Open group No comment provided by engineer. + + Open migration to another device + authentication reason + Open user profiles Avaa käyttäjäprofiilit @@ -3707,10 +3895,18 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or paste archive link + No comment provided by engineer. + Or scan QR code No comment provided by engineer. + + Or securely share this file link + No comment provided by engineer. + Or show this code No comment provided by engineer. @@ -3791,6 +3987,10 @@ This is your link for group %@! Pysyvä salauksen purkuvirhe message decrypt error item + + Picture-in-picture calls + No comment provided by engineer. + Please ask your contact to enable sending voice messages. Pyydä kontaktiasi sallimaan ääniviestien lähettäminen. @@ -3811,6 +4011,10 @@ This is your link for group %@! Tarkista omasi ja kontaktin asetukset. No comment provided by engineer. + + Please confirm that network settings are correct for this device. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3866,6 +4070,10 @@ Error: %@ Palvelimen osoitteen varmenteen sormenjälki on mahdollisesti virheellinen server test error + + Post-quantum E2EE + No comment provided by engineer. + Preserve the last message draft, with attachments. Säilytä viimeinen viestiluonnos liitteineen. @@ -3998,6 +4206,14 @@ Error: %@ Push-ilmoitukset No comment provided by engineer. + + Push server + No comment provided by engineer. + + + Quantum resistant encryption + No comment provided by engineer. + Rate the app Arvioi sovellus @@ -4180,10 +4396,22 @@ Error: %@ Repeat connection request? No comment provided by engineer. + + Repeat download + No comment provided by engineer. + + + Repeat import + No comment provided by engineer. + Repeat join request? No comment provided by engineer. + + Repeat upload + No comment provided by engineer. + Reply Vastaa @@ -4283,6 +4511,10 @@ Error: %@ SMP-palvelimet No comment provided by engineer. + + Safer groups + No comment provided by engineer. + Save Tallenna @@ -4641,6 +4873,10 @@ Error: %@ Aseta pääsykoodi No comment provided by engineer. + + Set passphrase + No comment provided by engineer. + Set passphrase to export Aseta tunnuslause vientiä varten @@ -4695,6 +4931,10 @@ Error: %@ Jaa kontaktien kanssa No comment provided by engineer. + + Show QR code + No comment provided by engineer. + Show calls in phone history Näytä puhelut puhelinhistoriassa @@ -4833,6 +5073,10 @@ Error: %@ Lopeta SimpleX authentication reason + + Stop chat + No comment provided by engineer. + Stop chat to enable database actions Pysäytä keskustelu tietokantatoimien mahdollistamiseksi @@ -4873,6 +5117,10 @@ Error: %@ Lopeta osoitteen jakaminen? No comment provided by engineer. + + Stopping chat + No comment provided by engineer. + Submit Lähetä @@ -5115,6 +5363,14 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Tätä toimintoa ei voi kumota - profiilisi, kontaktisi, viestisi ja tiedostosi poistuvat peruuttamattomasti. No comment provided by engineer. + + This chat is protected by end-to-end encryption. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + E2EE info chat item + This device name No comment provided by engineer. @@ -5394,11 +5650,19 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Päivitä ja avaa keskustelu No comment provided by engineer. + + Upload failed + No comment provided by engineer. + Upload file Lataa tiedosto server test step + + Uploading archive + No comment provided by engineer. + Use .onion hosts Käytä .onion-isäntiä @@ -5447,6 +5711,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Käytä palvelinta No comment provided by engineer. + + Use the app while in the call. + No comment provided by engineer. + User profile Käyttäjäprofiili @@ -5479,6 +5747,14 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Verify connections No comment provided by engineer. + + Verify database passphrase + No comment provided by engineer. + + + Verify passphrase + No comment provided by engineer. + Verify security code Tarkista turvakoodi @@ -5566,6 +5842,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Odottaa videota No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + No comment provided by engineer. + Warning: you may lose some data! Varoitus: saatat menettää joitain tietoja! @@ -5586,6 +5866,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Tervetuloviesti No comment provided by engineer. + + Welcome message is too long + No comment provided by engineer. + What's new Uusimmat @@ -5639,6 +5923,10 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Sinä No comment provided by engineer. + + You **must not** use the same database on two devices. + No comment provided by engineer. + You accepted connection Hyväksyit yhteyden @@ -5718,6 +6006,10 @@ Repeat join request? Voit ottaa ne käyttöön myöhemmin sovelluksen Yksityisyys & Turvallisuus -asetuksista. No comment provided by engineer. + + You can give another try. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. Voit piilottaa tai mykistää käyttäjäprofiilin pyyhkäisemällä sitä oikealle. @@ -6101,7 +6393,7 @@ SimpleX-palvelimet eivät näe profiiliasi. blocked - No comment provided by engineer. + marked deleted chat item preview text blocked %@ @@ -6109,7 +6401,7 @@ SimpleX-palvelimet eivät näe profiiliasi. blocked by admin - blocked chat item + marked deleted chat item preview text bold @@ -6535,7 +6827,7 @@ SimpleX-palvelimet eivät näe profiiliasi. moderated by %@ %@ moderoi - No comment provided by engineer. + marked deleted chat item preview text months @@ -6604,6 +6896,10 @@ SimpleX-palvelimet eivät näe profiiliasi. vertais No comment provided by engineer. + + quantum resistant e2e encryption + chat item text + received answer… vastaus saatu… @@ -6674,6 +6970,10 @@ SimpleX-palvelimet eivät näe profiiliasi. set new profile picture profile update event chat item + + standard end-to-end encryption + chat item text + starting… alkaa… diff --git a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff index 12c10dd0c8..7bdbefedb6 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -107,6 +107,11 @@ %@ connecté(e) No comment provided by engineer. + + %@ downloaded + %@ téléchargé + No comment provided by engineer. + %@ is connected! %@ est connecté·e ! @@ -127,6 +132,11 @@ Serveurs %@ No comment provided by engineer. + + %@ uploaded + %@ envoyé + No comment provided by engineer. + %@ wants to connect! %@ veut se connecter ! @@ -219,6 +229,7 @@ %lld messages blocked by admin + %lld messages bloqués par l'administrateur No comment provided by engineer. @@ -341,6 +352,11 @@ **Confidentiel** : ne pas utiliser le serveur de notifications SimpleX, vérification de nouveaux messages periodiquement en arrière plan (dépend de l'utilisation de l'app). No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Remarque** : l'utilisation de la même base de données sur deux appareils interrompt le déchiffrement des messages provenant de vos connexions, par mesure de sécurité. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Veuillez noter** : vous NE pourrez PAS récupérer ou modifier votre phrase secrète si vous la perdez. @@ -356,6 +372,11 @@ **Avertissement** : les notifications push instantanées nécessitent une phrase secrète enregistrée dans la keychain. No comment provided by engineer. + + **Warning**: the archive will be removed. + **Avertissement** : l'archive sera supprimée. + No comment provided by engineer. + **e2e encrypted** audio call appel audio **chiffré de bout en bout** @@ -612,6 +633,11 @@ Le changement d'adresse sera annulé. L'ancienne adresse de réception sera utilisée. No comment provided by engineer. + + Admins can block a member for all. + Les admins peuvent bloquer un membre pour tous. + No comment provided by engineer. + Admins can create the links to join groups. Les admins peuvent créer les liens qui permettent de rejoindre les groupes. @@ -667,6 +693,11 @@ Tous vos contacts resteront connectés. La mise à jour du profil sera envoyée à vos contacts. No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Tous vos contacts, conversations et fichiers seront chiffrés en toute sécurité et transférés par morceaux vers les relais XFTP configurés. + No comment provided by engineer. + Allow Autoriser @@ -792,6 +823,11 @@ Build de l'app : %@ No comment provided by engineer. + + App data migration + Transfert des données de l'application + No comment provided by engineer. + App encrypts new local files (except videos). L'application chiffre les nouveaux fichiers locaux (sauf les vidéos). @@ -827,6 +863,21 @@ Apparence No comment provided by engineer. + + Apply + Appliquer + No comment provided by engineer. + + + Archive and upload + Archiver et transférer + No comment provided by engineer. + + + Archiving database + Archivage de la base de données + No comment provided by engineer. + Attach Attacher @@ -924,6 +975,7 @@ Block for all + Bloqué pour tous No comment provided by engineer. @@ -938,6 +990,7 @@ Block member for all? + Bloquer le membre pour tous ? No comment provided by engineer. @@ -947,6 +1000,7 @@ Blocked by admin + Bloqué par l'administrateur No comment provided by engineer. @@ -1014,6 +1068,11 @@ Annuler No comment provided by engineer. + + Cancel migration + Annuler le transfert + No comment provided by engineer. + Cannot access keychain to save database password Impossible d'accéder à la keychain pour enregistrer le mot de passe de la base de données @@ -1115,6 +1174,11 @@ Le chat est arrêté. Si vous avez déjà utilisé cette base de données sur un autre appareil, vous devez la transférer à nouveau avant de démarrer le chat. No comment provided by engineer. + + Chat migrated! + Messagerie transférée ! + No comment provided by engineer. + Chat preferences Préférences de chat @@ -1135,6 +1199,11 @@ Interface en chinois et en espagnol No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + Choisissez _Transferer depuis un autre appareil_ sur le nouvel appareil et scannez le code QR. + No comment provided by engineer. + Choose file Choisir le fichier @@ -1205,6 +1274,11 @@ Confirmer la mise à niveau de la base de données No comment provided by engineer. + + Confirm network settings + Confirmer les paramètres réseau + No comment provided by engineer. + Confirm new passphrase… Confirmer la nouvelle phrase secrète… @@ -1215,6 +1289,16 @@ Confirmer le mot de passe No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + Confirmer que vous vous souvenez de la phrase secrète de la base de données pour la transférer. + No comment provided by engineer. + + + Confirm upload + Confirmer la transmission + No comment provided by engineer. + Connect Se connecter @@ -1411,7 +1495,7 @@ Il s'agit de votre propre lien unique ! Create an address to let people connect with you. - Créez une adresse pour permettre aux gens de vous contacter. + Vous pouvez créer une adresse pour permettre aux autres utilisateurs de vous contacter. No comment provided by engineer. @@ -1474,6 +1558,11 @@ Il s'agit de votre propre lien unique ! Créé le %@ No comment provided by engineer. + + Creating archive link + Création d'un lien d'archive + No comment provided by engineer. + Creating link… Création d'un lien… @@ -1694,6 +1783,11 @@ Cette opération ne peut être annulée ! Supprimer la base de données No comment provided by engineer. + + Delete database from this device + Supprimer la base de données de cet appareil + No comment provided by engineer. + Delete file Supprimer le fichier @@ -1984,11 +2078,26 @@ Cette opération ne peut être annulée ! Rétrograder et ouvrir le chat No comment provided by engineer. + + Download failed + Échec du téléchargement + No comment provided by engineer. + Download file Télécharger le fichier server test step + + Downloading archive + Téléchargement de l'archive + No comment provided by engineer. + + + Downloading link details + Téléchargement des détails du lien + No comment provided by engineer. + Duplicate display name! Nom d'affichage en double ! @@ -2044,6 +2153,11 @@ Cette opération ne peut être annulée ! Activer pour tous No comment provided by engineer. + + Enable in direct chats (BETA)! + Activé dans les conversations directes (BETA) ! + No comment provided by engineer. + Enable instant notifications? Activer les notifications instantanées ? @@ -2159,6 +2273,11 @@ Cette opération ne peut être annulée ! Entrer un nom de groupe… No comment provided by engineer. + + Enter passphrase + Entrer la phrase secrète + No comment provided by engineer. + Enter passphrase… Entrez la phrase secrète… @@ -2219,6 +2338,11 @@ Cette opération ne peut être annulée ! Erreur lors de l'ajout de membre·s No comment provided by engineer. + + Error allowing contact PQ encryption + Erreur lors de la négociation du chiffrement PQ + No comment provided by engineer. + Error changing address Erreur de changement d'adresse @@ -2309,6 +2433,11 @@ Cette opération ne peut être annulée ! Erreur lors de la suppression du profil utilisateur No comment provided by engineer. + + Error downloading the archive + Erreur lors du téléchargement de l'archive + No comment provided by engineer. + Error enabling delivery receipts! Erreur lors de l'activation des accusés de réception ! @@ -2384,6 +2513,11 @@ Cette opération ne peut être annulée ! Erreur lors de l'enregistrement de la phrase de passe dans la keychain No comment provided by engineer. + + Error saving settings + Erreur lors de l'enregistrement des paramètres + when migrating + Error saving user password Erreur d'enregistrement du mot de passe de l'utilisateur @@ -2454,6 +2588,16 @@ Cette opération ne peut être annulée ! Erreur de mise à jour de la confidentialité de l'utilisateur No comment provided by engineer. + + Error uploading the archive + Erreur lors de l'envoi de l'archive + No comment provided by engineer. + + + Error verifying passphrase: + Erreur lors de la vérification de la phrase secrète : + No comment provided by engineer. + Error: Erreur : @@ -2486,7 +2630,7 @@ Cette opération ne peut être annulée ! Expand - Développer + Étendre chat item action @@ -2504,6 +2648,11 @@ Cette opération ne peut être annulée ! Archive de la base de données exportée. No comment provided by engineer. + + Exported file doesn't exist + Le fichier exporté n'existe pas + No comment provided by engineer. + Exporting database archive… Exportation de l'archive de la base de données… @@ -2574,6 +2723,16 @@ Cette opération ne peut être annulée ! Filtrer les messages non lus et favoris. No comment provided by engineer. + + Finalize migration + Finaliser le transfert + No comment provided by engineer. + + + Finalize migration on another device. + Finalisez le transfert sur l'autre appareil. + No comment provided by engineer. + Finally, we have them! 🚀 Enfin, les voilà ! 🚀 @@ -2864,6 +3023,11 @@ Cette opération ne peut être annulée ! Comment utiliser vos serveurs No comment provided by engineer. + + Hungarian interface + Interface en hongrois + No comment provided by engineer. + ICE servers (one per line) Serveurs ICE (un par ligne) @@ -2929,6 +3093,16 @@ Cette opération ne peut être annulée ! Importer la base de données No comment provided by engineer. + + Import failed + Échec de l'importation + No comment provided by engineer. + + + Importing archive + Importation de l'archive + No comment provided by engineer. + Improved message delivery Amélioration de la transmission des messages @@ -2944,6 +3118,11 @@ Cette opération ne peut être annulée ! Configuration de serveur améliorée No comment provided by engineer. + + In order to continue, chat should be stopped. + Pour continuer, le chat doit être interrompu. + No comment provided by engineer. + In reply to En réponse à @@ -3056,6 +3235,11 @@ Cette opération ne peut être annulée ! Lien invalide No comment provided by engineer. + + Invalid migration confirmation + Confirmation de migration invalide + No comment provided by engineer. + Invalid name! Nom invalide ! @@ -3424,6 +3608,11 @@ Voici votre lien pour le groupe %@ ! Texte du message No comment provided by engineer. + + Message too large + Message trop volumineux + No comment provided by engineer. + Messages Messages @@ -3439,11 +3628,56 @@ Voici votre lien pour le groupe %@ ! Les messages de %@ seront affichés ! No comment provided by engineer. + + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + Les messages, fichiers et appels sont protégés par un chiffrement **de bout en bout** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction. + 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. + Les messages, fichiers et appels sont protégés par un chiffrement **2e2 résistant post-quantique** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction. + No comment provided by engineer. + + + Migrate device + Transférer l'appareil + No comment provided by engineer. + + + Migrate from another device + Transférer depuis un autre appareil + No comment provided by engineer. + + + Migrate here + Transférer ici + No comment provided by engineer. + + + Migrate to another device + Transférer vers un autre appareil + No comment provided by engineer. + + + Migrate to another device via QR code. + Transférer vers un autre appareil via un code QR. + No comment provided by engineer. + + + Migrating + Transfert + No comment provided by engineer. + Migrating database archive… Migration de l'archive de la base de données… No comment provided by engineer. + + Migration complete + Transfert terminé + No comment provided by engineer. + Migration error: Erreur de migration : @@ -3803,6 +4037,11 @@ Voici votre lien pour le groupe %@ ! Ouvrir le groupe No comment provided by engineer. + + Open migration to another device + Ouvrir le transfert vers un autre appareil + authentication reason + Open user profiles Ouvrir les profils d'utilisateurs @@ -3818,11 +4057,21 @@ Voici votre lien pour le groupe %@ ! Ouverture de l'app… No comment provided by engineer. + + Or paste archive link + Ou coller le lien de l'archive + No comment provided by engineer. + Or scan QR code Ou scanner le code QR No comment provided by engineer. + + Or securely share this file link + Ou partagez en toute sécurité le lien de ce fichier + No comment provided by engineer. + Or show this code Ou présenter ce code @@ -3908,6 +4157,11 @@ Voici votre lien pour le groupe %@ ! Erreur de déchiffrement message decrypt error item + + Picture-in-picture calls + Appels picture-in-picture + No comment provided by engineer. + Please ask your contact to enable sending voice messages. Veuillez demander à votre contact de permettre l'envoi de messages vocaux. @@ -3928,6 +4182,11 @@ Voici votre lien pour le groupe %@ ! Veuillez vérifier vos préférences ainsi que celles de votre contact. No comment provided by engineer. + + Please confirm that network settings are correct for this device. + Veuillez confirmer que les paramètres réseau de cet appareil sont corrects. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3985,6 +4244,11 @@ Erreur : %@ Il est possible que l'empreinte du certificat dans l'adresse du serveur soit incorrecte server test error + + Post-quantum E2EE + E2EE post-quantique + No comment provided by engineer. + Preserve the last message draft, with attachments. Conserver le brouillon du dernier message, avec les pièces jointes. @@ -4120,6 +4384,16 @@ Erreur : %@ Notifications push No comment provided by engineer. + + Push server + Serveur Push + No comment provided by engineer. + + + Quantum resistant encryption + Chiffrement résistant post-quantique + No comment provided by engineer. + Rate the app Évaluer l'app @@ -4305,11 +4579,26 @@ Erreur : %@ Répéter la demande de connexion ? No comment provided by engineer. + + Repeat download + Répéter le téléchargement + No comment provided by engineer. + + + Repeat import + Répéter l'importation + No comment provided by engineer. + Repeat join request? Répéter la requête d'adhésion ? No comment provided by engineer. + + Repeat upload + Répéter l'envoi + No comment provided by engineer. + Reply Répondre @@ -4410,6 +4699,11 @@ Erreur : %@ Serveurs SMP No comment provided by engineer. + + Safer groups + Groupes plus sûrs + No comment provided by engineer. + Save Enregistrer @@ -4775,6 +5069,11 @@ Erreur : %@ Définir le code d'accès No comment provided by engineer. + + Set passphrase + Définir une phrase secrète + No comment provided by engineer. + Set passphrase to export Définir la phrase secrète pour l'export @@ -4830,6 +5129,11 @@ Erreur : %@ Partager avec vos contacts No comment provided by engineer. + + Show QR code + Afficher le code QR + No comment provided by engineer. + Show calls in phone history Afficher les appels dans l'historique du téléphone @@ -4847,7 +5151,7 @@ Erreur : %@ Show preview - Montrer l'aperçu + Afficher l'aperçu No comment provided by engineer. @@ -4970,6 +5274,11 @@ Erreur : %@ Arrêter SimpleX authentication reason + + Stop chat + Arrêter le chat + No comment provided by engineer. + Stop chat to enable database actions Arrêter le chat pour permettre des actions sur la base de données @@ -5010,6 +5319,11 @@ Erreur : %@ Cesser le partage d'adresse ? No comment provided by engineer. + + Stopping chat + Arrêt du chat + No comment provided by engineer. + Submit Soumettre @@ -5257,6 +5571,16 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Cette action ne peut être annulée - votre profil, vos contacts, vos messages et vos fichiers seront irréversiblement perdus. No comment provided by engineer. + + This chat is protected by end-to-end encryption. + Cette discussion est protégée par un chiffrement de bout en bout. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + Cette discussion est protégée par un chiffrement de bout en bout résistant post-quantique. + E2EE info chat item + This device name Nom de cet appareil @@ -5396,6 +5720,7 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Unblock for all + Débloquer pour tous No comment provided by engineer. @@ -5405,6 +5730,7 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Unblock member for all? + Débloquer le membre pour tous ? No comment provided by engineer. @@ -5549,11 +5875,21 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Mettre à niveau et ouvrir le chat No comment provided by engineer. + + Upload failed + Échec de l'envoi + No comment provided by engineer. + Upload file Transférer le fichier server test step + + Uploading archive + Envoi de l'archive + No comment provided by engineer. + Use .onion hosts Utiliser les hôtes .onions @@ -5604,6 +5940,11 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Utiliser ce serveur No comment provided by engineer. + + Use the app while in the call. + Utiliser l'application pendant l'appel. + No comment provided by engineer. + User profile Profil d'utilisateur @@ -5616,7 +5957,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Using SimpleX Chat servers. - Utilisation des serveurs SimpleX Chat. + Vous utilisez les serveurs SimpleX. No comment provided by engineer. @@ -5639,6 +5980,16 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Vérifier les connexions No comment provided by engineer. + + Verify database passphrase + Vérifier la phrase secrète de la base de données + No comment provided by engineer. + + + Verify passphrase + Vérifier la phrase secrète + No comment provided by engineer. + Verify security code Vérifier le code de sécurité @@ -5729,6 +6080,11 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien En attente de la vidéo No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Attention : démarrer une session de chat sur plusieurs appareils n'est pas pris en charge et entraînera des dysfonctionnements au niveau de la transmission des messages + No comment provided by engineer. + Warning: you may lose some data! Attention : vous risquez de perdre des données ! @@ -5749,6 +6105,11 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Message de bienvenue No comment provided by engineer. + + Welcome message is too long + Le message de bienvenue est trop long + No comment provided by engineer. + What's new Quoi de neuf ? @@ -5761,7 +6122,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien When people request to connect, you can accept or reject it. - Lorsque des personnes demandent à se connecter, vous pouvez les accepter ou les refuser. + Vous pouvez accepter ou refuser les demandes de contacts. No comment provided by engineer. @@ -5804,6 +6165,11 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Vous No comment provided by engineer. + + You **must not** use the same database on two devices. + Vous **ne devez pas** utiliser la même base de données sur deux appareils. + No comment provided by engineer. + You accepted connection Vous avez accepté la connexion @@ -5891,6 +6257,11 @@ Répéter la demande d'adhésion ? Vous pouvez les activer ultérieurement via les paramètres de Confidentialité et Sécurité de l'application. No comment provided by engineer. + + You can give another try. + Vous pouvez faire un nouvel essai. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. Vous pouvez masquer ou mettre en sourdine un profil d'utilisateur - faites-le glisser vers la droite. @@ -5923,7 +6294,7 @@ Répéter la demande d'adhésion ? You can share your address as a link or QR code - anybody can connect to you. - Vous pouvez partager votre adresse sous forme de lien ou de code QR - n'importe qui pourra se connecter à vous. + Vous pouvez partager votre adresse sous la forme d'un lien ou d'un code QR - tout le monde peut l'utiliser pour vous contacter. No comment provided by engineer. @@ -6244,12 +6615,12 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. agreeing encryption for %@… - accord sur le chiffrement pour %@… + négociation du chiffrement avec %@… chat item text agreeing encryption… - accord sur le chiffrement… + négociation du chiffrement… chat item text @@ -6285,15 +6656,17 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. blocked blocké - No comment provided by engineer. + marked deleted chat item preview text blocked %@ + %@ bloqué rcv group event chat item blocked by admin - blocked chat item + bloqué par l'administrateur + marked deleted chat item preview text bold @@ -6322,7 +6695,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. changed address for you - adresse modifiée pour vous + changement de l'adresse du contact chat item text @@ -6723,7 +7096,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. moderated by %@ modéré par %@ - No comment provided by engineer. + marked deleted chat item preview text months @@ -6792,6 +7165,11 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. pair-à-pair No comment provided by engineer. + + quantum resistant e2e encryption + chiffrement e2e résistant post-quantique + chat item text + received answer… réponse reçu… @@ -6859,14 +7237,19 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. set new contact address - définir une nouvelle adresse de contact + a changé d'adresse de contact profile update event chat item set new profile picture - définir une nouvelle image de profil + a changé d'image de profil profile update event chat item + + standard end-to-end encryption + chiffrement de bout en bout standard + chat item text + starting… lancement… @@ -6884,6 +7267,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. unblocked %@ + %@ débloqué rcv group event chat item @@ -6978,6 +7362,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. you blocked %@ + vous avez bloqué %@ snd group event chat item @@ -7022,6 +7407,7 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. you unblocked %@ + vous avez débloqué %@ snd group event chat item diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/Shared/Assets.xcassets/AccentColor.colorset/Contents.json b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/Shared/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000000..84d4c2f1c9 --- /dev/null +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/Shared/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,15 @@ +{ + "colors" : [ + { + "idiom" : "universal", + "locale" : "hu" + } + ], + "properties" : { + "localizable" : true + }, + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/Shared/Assets.xcassets/Contents.json b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/Shared/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/Shared/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff new file mode 100644 index 0000000000..546bdf23d3 --- /dev/null +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -0,0 +1,7409 @@ + + + +
+ +
+ + + + + + + No comment provided by engineer. + + + + + No comment provided by engineer. + + + + + No comment provided by engineer. + + + + + No comment provided by engineer. + + + ( + ( + No comment provided by engineer. + + + (can be copied) + (másolható) + No comment provided by engineer. + + + !1 colored! + !1 színezett! + No comment provided by engineer. + + + # %@ + # %@ + copied message info title, # <title> + + + ## History + ## Előzmények + copied message info + + + ## In reply to + ## Válaszul erre + copied message info + + + #secret# + #titkos# + No comment provided by engineer. + + + %@ + %@ + No comment provided by engineer. + + + %@ %@ + %@ %@ + No comment provided by engineer. + + + %@ (current) + %@ (jelenlegi) + No comment provided by engineer. + + + %@ (current): + %@ (jelenlegi): + copied message info + + + %@ / %@ + %@ / %@ + No comment provided by engineer. + + + %@ and %@ + %@ és %@ + No comment provided by engineer. + + + %@ and %@ connected + %@ és %@ csatlakozott + No comment provided by engineer. + + + %1$@ at %2$@: + %1$@ %2$@-kor: + copied message info, <sender> at <time> + + + %@ connected + %@ csatlakozott + No comment provided by engineer. + + + %@ downloaded + No comment provided by engineer. + + + %@ is connected! + %@ csatlakozott! + notification title + + + %@ is not verified + %@ nem ellenőrzött + No comment provided by engineer. + + + %@ is verified + %@ ellenőrizve + No comment provided by engineer. + + + %@ servers + %@ kiszolgáló + No comment provided by engineer. + + + %@ uploaded + No comment provided by engineer. + + + %@ wants to connect! + %@ csatlakozni szeretne! + notification title + + + %@, %@ and %lld members + %@, %@ és további %lld tag + No comment provided by engineer. + + + %@, %@ and %lld other members connected + %@, %@ és további %lld tag csatlakozott + No comment provided by engineer. + + + %@: + %@: + copied message info + + + %d days + %d nap + time interval + + + %d hours + %d óra + time interval + + + %d min + %d perc + time interval + + + %d months + %d hónap + time interval + + + %d sec + %d mp + time interval + + + %d skipped message(s) + %d kihagyott üzenet + integrity error chat item + + + %d weeks + %d hét + time interval + + + %lld + %lld + No comment provided by engineer. + + + %lld %@ + %lld %@ + No comment provided by engineer. + + + %lld contact(s) selected + %lld ismerős kiválasztva + No comment provided by engineer. + + + %lld file(s) with total size of %@ + %lld fájl, amely(ek)nek teljes mérete: %@ + No comment provided by engineer. + + + %lld group events + %lld csoportesemény + No comment provided by engineer. + + + %lld members + %lld tag + No comment provided by engineer. + + + %lld messages blocked + %lld üzenet blokkolva + No comment provided by engineer. + + + %lld messages blocked by admin + %lld üzenet blokkolva az admin által + No comment provided by engineer. + + + %lld messages marked deleted + %lld törlésre megjelölt üzenet + No comment provided by engineer. + + + %lld messages moderated by %@ + %@ %lld üzenetet moderált + No comment provided by engineer. + + + %lld minutes + %lld perc + No comment provided by engineer. + + + %lld new interface languages + %lld új nyelvi csomag + No comment provided by engineer. + + + %lld second(s) + %lld másodperc + No comment provided by engineer. + + + %lld seconds + %lld másodperc + No comment provided by engineer. + + + %lldd + %lldd + No comment provided by engineer. + + + %lldh + %lldh + No comment provided by engineer. + + + %lldk + %lldk + No comment provided by engineer. + + + %lldm + %lldm + No comment provided by engineer. + + + %lldmth + %lldmth + No comment provided by engineer. + + + %llds + %llds + No comment provided by engineer. + + + %lldw + %lldw + No comment provided by engineer. + + + %u messages failed to decrypt. + %u üzenet visszafejtése sikertelen. + No comment provided by engineer. + + + %u messages skipped. + %u kihagyott üzenet. + No comment provided by engineer. + + + ( + ( + No comment provided by engineer. + + + (new) + (új) + No comment provided by engineer. + + + (this device v%@) + (ez az eszköz v%@) + No comment provided by engineer. + + + ) + ) + No comment provided by engineer. + + + **Add contact**: to create a new invitation link, or connect via a link you received. + **Ismerős hozzáadása**: új meghívó hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő csatlakozáshoz. + No comment provided by engineer. + + + **Add new contact**: to create your one-time QR Code or link for your contact. + **Új ismerős hozzáadása**: egyszer használatos QR-kód vagy hivatkozás létrehozása a kapcsolattartóhoz. + No comment provided by engineer. + + + **Create group**: to create a new group. + **Csoport létrehozása**: új csoport létrehozásához. + No comment provided by engineer. + + + **More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have. + **Privátabb**: 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van. + No comment provided by engineer. + + + **Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app). + **Legprivátabb**: ne használja a SimpleX Chat értesítési szervert, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást). + No comment provided by engineer. + + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + No comment provided by engineer. + + + **Please note**: you will NOT be able to recover or change passphrase if you lose it. + **Figyelem**: NEM tudja visszaállítani vagy megváltoztatni jelmondatát, ha elveszíti azt. + No comment provided by engineer. + + + **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. + **Javasolt**: az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési szerverre, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik. + No comment provided by engineer. + + + **Warning**: Instant push notifications require passphrase saved in Keychain. + **Figyelmeztetés**: Az azonnali push-értesítésekhez a kulcstárolóban tárolt jelmondat megadása szükséges. + No comment provided by engineer. + + + **Warning**: the archive will be removed. + No comment provided by engineer. + + + **e2e encrypted** audio call + **e2e titkosított** hanghívás + No comment provided by engineer. + + + **e2e encrypted** video call + **e2e titkosított** videóhívás + No comment provided by engineer. + + + \*bold* + \*félkövér* + No comment provided by engineer. + + + , + , + No comment provided by engineer. + + + - connect to [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)! +- delivery receipts (up to 20 members). +- faster and more stable. + - kapcsolódás a [könyvtár szolgáltatáshoz] (simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2Ld3%3DWpxkKFeXSPv3pwp %2F%3Fv%3D1-2%26dh %3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6glco6bqjETA)4Beklco6bqj) +- kézbesítési jelentések (legfeljebb 20 tag). +- gyorsabb és stabilabb. + No comment provided by engineer. + + + - more stable message delivery. +- a bit better groups. +- and more! + - stabilabb üzenetkézbesítés. +- valamivel jobb csoportok. +- és még sok más! + No comment provided by engineer. + + + - optionally notify deleted contacts. +- profile names with spaces. +- and more! + - opcionális értesítés a törölt kapcsolatokról. +- profilnevek szóközökkel. +- és még sok más! + No comment provided by engineer. + + + - voice messages up to 5 minutes. +- custom time to disappear. +- editing history. + - hangüzenetek legfeljebb 5 perces időtartamig. +- egyedi eltűnési időhatár megadása. +- előzmények szerkesztése. + No comment provided by engineer. + + + . + . + No comment provided by engineer. + + + 0 sec + 0 mp + time to disappear + + + 0s + 0s + No comment provided by engineer. + + + 1 day + 1 nap + time interval + + + 1 hour + 1 óra + time interval + + + 1 minute + 1 perc + No comment provided by engineer. + + + 1 month + 1 hónap + time interval + + + 1 week + 1 hét + time interval + + + 5 minutes + 5 perc + No comment provided by engineer. + + + 6 + 6 + No comment provided by engineer. + + + 30 seconds + 30 másodperc + No comment provided by engineer. + + + : + : + No comment provided by engineer. + + + <p>Hi!</p> +<p><a href="%@">Connect to me via SimpleX Chat</a></p> + <p>Üdvözlöm!</p> +<p><a href="%@">Csatlakozzon hozzám a SimpleX Chaten</a></p> + email text + + + A few more things + Még néhány dolog + No comment provided by engineer. + + + A new contact + Egy új ismerős + notification title + + + A new random profile will be shared. + Egy új, véletlenszerű profil kerül megosztásra. + No comment provided by engineer. + + + A separate TCP connection will be used **for each chat profile you have in the app**. + A rendszer külön TCP-kapcsolatot fog használni **az alkalmazásban található minden csevegési profilhoz**. + No comment provided by engineer. + + + A separate TCP connection will be used **for each contact and group member**. +**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail. + A rendszer külön TCP-kapcsolatot fog használni **minden ismerőshöz és csoporttaghoz**. +**Figyelem**: sok kapcsolódás esetén, az akkumulátor- és adatforgalom fogyasztás jelentősen megnőhet, és egyes kapcsolatok meghiúsulhatnak. + No comment provided by engineer. + + + Abort + Megszakítás + No comment provided by engineer. + + + Abort changing address + Címváltoztatás megszakítása + No comment provided by engineer. + + + Abort changing address? + Címváltoztatás megszakítása?? + No comment provided by engineer. + + + About SimpleX + A SimpleX névjegye + No comment provided by engineer. + + + About SimpleX Chat + A SimpleX Chat névjegye + No comment provided by engineer. + + + About SimpleX address + A SimpleX azonosítóról + No comment provided by engineer. + + + Accent color + Kiemelő szín + No comment provided by engineer. + + + Accept + Elfogadás + accept contact request via notification + accept incoming call via notification + + + Accept connection request? + Kapcsolatfelvétel elfogadása? + No comment provided by engineer. + + + Accept contact request from %@? + Elfogadja %@ kapcsolat kérését? + notification body + + + Accept incognito + Fogadás inkognítóban + accept contact request via notification + + + Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts. + Azonosító hozzáadása a profilhoz, hogy az ismerősök megoszthassák másokkal. A profilfrissítés elküldésre kerül ismerősők számára. + No comment provided by engineer. + + + Add contact + Ismerős hozzáadása + No comment provided by engineer. + + + Add preset servers + Előre beállított kiszolgálók hozzáadása + No comment provided by engineer. + + + Add profile + Profil hozzáadása + No comment provided by engineer. + + + Add servers by scanning QR codes. + Kiszolgáló hozzáadása QR-kód beolvasásával. + No comment provided by engineer. + + + Add server… + Kiszolgáló hozzáadása… + No comment provided by engineer. + + + Add to another device + Hozzáadás egy másik eszközhöz + No comment provided by engineer. + + + Add welcome message + Üdvözlő üzenet hozzáadása + No comment provided by engineer. + + + Address + Cím + No comment provided by engineer. + + + Address change will be aborted. Old receiving address will be used. + A cím módosítása megszakad. A régi fogadási cím kerül felhasználásra. + No comment provided by engineer. + + + Admins can block a member for all. + No comment provided by engineer. + + + Admins can create the links to join groups. + Az adminok hivatkozásokat hozhatnak létre a csoportokhoz való csatlakozáshoz. + No comment provided by engineer. + + + Advanced network settings + Speciális hálózati beállítások + No comment provided by engineer. + + + All app data is deleted. + Minden alkalmazásadat törölve. + No comment provided by engineer. + + + All chats and messages will be deleted - this cannot be undone! + Minden csevegés és üzenet törlésre kerül - ez nem vonható vissza! + No comment provided by engineer. + + + All data is erased when it is entered. + A jelkód megadása után minden adat törlésre kerül. + No comment provided by engineer. + + + All group members will remain connected. + Minden csoporttag csatlakoztatva marad. + No comment provided by engineer. + + + All messages will be deleted - this cannot be undone! + Minden üzenet törlésre kerül – ez nem vonható vissza! + No comment provided by engineer. + + + All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you. + Minden üzenet törlésre kerül - ezt nem vonható vissza! Az üzenetek CSAK az ön számára törlődnek. + No comment provided by engineer. + + + All new messages from %@ will be hidden! + Minden új üzenet elrejtésre kerül tőle: %@! + No comment provided by engineer. + + + All your contacts will remain connected. + Minden ismerős csatlakoztatva marad. + No comment provided by engineer. + + + All your contacts will remain connected. Profile update will be sent to your contacts. + 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. + No comment provided by engineer. + + + Allow + Engedélyezés + No comment provided by engineer. + + + Allow calls only if your contact allows them. + Hívások engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi. + No comment provided by engineer. + + + Allow disappearing messages only if your contact allows it to you. + Eltűnő üzenetek engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi az ön számára. + No comment provided by engineer. + + + Allow irreversible message deletion only if your contact allows it to you. (24 hours) + Üzenet végleges törlésének engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi. (24 óra) + No comment provided by engineer. + + + Allow message reactions only if your contact allows them. + Üzenetreakciók engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi. + No comment provided by engineer. + + + Allow message reactions. + Üzenetreakciók engedélyezése. + No comment provided by engineer. + + + Allow sending direct messages to members. + Közvetlen üzenetek küldésének engedélyezése tagok részére. + No comment provided by engineer. + + + Allow sending disappearing messages. + Eltűnő üzenetek küldésének engedélyezése. + No comment provided by engineer. + + + Allow to irreversibly delete sent messages. (24 hours) + Elküldött üzenetek visszafordíthatatlan törlésének engedélyezése. (24 óra) + No comment provided by engineer. + + + Allow to send files and media. + Fájlok és médiatartalom küldésének engedélyezése. + No comment provided by engineer. + + + Allow to send voice messages. + Hangüzenetek küldésének engedélyezése. + No comment provided by engineer. + + + Allow voice messages only if your contact allows them. + Hangüzenetek küldésének engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi. + No comment provided by engineer. + + + Allow voice messages? + Hangüzenetek engedélyezése? + No comment provided by engineer. + + + Allow your contacts adding message reactions. + Ismerősök általi üzenetreakciók hozzáadásának engedélyezése. + No comment provided by engineer. + + + Allow your contacts to call you. + Hívások engedélyezése ismerősök számára. + No comment provided by engineer. + + + Allow your contacts to irreversibly delete sent messages. (24 hours) + Elküldött üzenetek visszafordíthatatlan törlésének engedélyezése ismerősök számára. (24 óra) + No comment provided by engineer. + + + Allow your contacts to send disappearing messages. + Eltűnő üzenetek engedélyezése ismerősök számára. + No comment provided by engineer. + + + Allow your contacts to send voice messages. + Hangüzenetek küldésének engedélyezése ismerősök számára. + No comment provided by engineer. + + + Already connected? + Csatlakoztatva? + No comment provided by engineer. + + + Already connecting! + Kapcsolódás folyamatban! + No comment provided by engineer. + + + Already joining the group! + Csatlakozás folyamatban! + No comment provided by engineer. + + + Always use relay + Mindig használjon átjátszó kiszolgálót + No comment provided by engineer. + + + An empty chat profile with the provided name is created, and the app opens as usual. + Egy üres csevegési profil jön létre a megadott névvel, és az alkalmazás a szokásos módon megnyílik. + No comment provided by engineer. + + + Answer call + Hívás fogadása + No comment provided by engineer. + + + App build: %@ + Az alkalmazás build száma: %@ + No comment provided by engineer. + + + App data migration + No comment provided by engineer. + + + App encrypts new local files (except videos). + Az alkalmazás titkosítja a helyi fájlokat (a videók kivételével). + No comment provided by engineer. + + + App icon + Alkalmazás ikon + No comment provided by engineer. + + + App passcode + Alkalmazás jelkód + No comment provided by engineer. + + + App passcode is replaced with self-destruct passcode. + Az alkalmazás jelkód helyettesítésre kerül egy önmegsemmisítő jelkóddal. + No comment provided by engineer. + + + App version + Alkalmazás verzió + No comment provided by engineer. + + + App version: v%@ + Alkalmazás verzió: v%@ + No comment provided by engineer. + + + Appearance + Megjelenés + No comment provided by engineer. + + + Apply + No comment provided by engineer. + + + Archive and upload + No comment provided by engineer. + + + Archiving database + No comment provided by engineer. + + + Attach + Csatolás + No comment provided by engineer. + + + Audio & video calls + Hang- és videóhívások + No comment provided by engineer. + + + Audio and video calls + Hang- és videóhívások + No comment provided by engineer. + + + Audio/video calls + Hang-/videóhívások + chat feature + + + Audio/video calls are prohibited. + A hang- és videóhívások le vannak tiltva. + No comment provided by engineer. + + + Authentication cancelled + Hitelesítés megszakítva + PIN entry + + + Authentication failed + Hitelesítés sikertelen + No comment provided by engineer. + + + Authentication is required before the call is connected, but you may miss calls. + A hívás csatlakoztatása előtt hitelesítésre van szükség, de előfordulhat, hogy nem tud hívásokat fogadni. + No comment provided by engineer. + + + Authentication unavailable + Hitelesítés elérhetetlen + No comment provided by engineer. + + + Auto-accept + Automatikus elfogadás + No comment provided by engineer. + + + Auto-accept contact requests + Ismerős jelölések automatikus elfogadása + No comment provided by engineer. + + + Auto-accept images + Fotók automatikus elfogadása + No comment provided by engineer. + + + Back + Vissza + No comment provided by engineer. + + + Bad desktop address + Hibás számítógép azonosító + No comment provided by engineer. + + + Bad message ID + Téves üzenet ID + No comment provided by engineer. + + + Bad message hash + Téves üzenet hash + No comment provided by engineer. + + + Better groups + Javított csoportok + No comment provided by engineer. + + + Better messages + Jobb üzenetek + No comment provided by engineer. + + + Block + Blokkolás + No comment provided by engineer. + + + Block for all + Mindenki számára letiltva + No comment provided by engineer. + + + Block group members + Csoporttagok blokkolása + No comment provided by engineer. + + + Block member + Tag blokkolása + No comment provided by engineer. + + + Block member for all? + Tag letiltása mindenki számára? + No comment provided by engineer. + + + Block member? + Tag blokkolása? + No comment provided by engineer. + + + Blocked by admin + Letiltva az admin által + No comment provided by engineer. + + + Both you and your contact can add message reactions. + Mindkét fél is hozzáadhat üzenetreakciókat. + No comment provided by engineer. + + + Both you and your contact can irreversibly delete sent messages. (24 hours) + Mindkét fél visszafordíthatatlanul törölheti az elküldött üzeneteket. (24 óra) + No comment provided by engineer. + + + Both you and your contact can make calls. + Mindkét fél tud hívásokat indítani. + No comment provided by engineer. + + + Both you and your contact can send disappearing messages. + Mindkét fél küldhet eltűnő üzeneteket. + No comment provided by engineer. + + + Both you and your contact can send voice messages. + Mindkét fél küldhet hangüzeneteket. + No comment provided by engineer. + + + Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! + Bolgár, finn, thai és ukrán – köszönet a felhasználóknak és a [Weblate-nek](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! + No comment provided by engineer. + + + By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). + Csevegési profil (alapértelmezett) vagy [kapcsolat alapján] (https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BÉTA). + No comment provided by engineer. + + + Call already ended! + A hívás már befejeződött! + No comment provided by engineer. + + + Calls + Hívások + No comment provided by engineer. + + + Camera not available + A fényképező nem elérhető + No comment provided by engineer. + + + Can't invite contact! + Ismerősök meghívása le van tiltva! + No comment provided by engineer. + + + Can't invite contacts! + Ismerősök meghívása nem lehetséges! + No comment provided by engineer. + + + Cancel + Megszakítás + No comment provided by engineer. + + + Cancel migration + No comment provided by engineer. + + + Cannot access keychain to save database password + Nem lehet hozzáférni a kulcstartóhoz az adatbázis jelszavának mentéséhez + No comment provided by engineer. + + + Cannot receive file + Nem lehet fogadni a fájlt + No comment provided by engineer. + + + Change + Változtatás + No comment provided by engineer. + + + Change database passphrase? + Adatbázis jelmondat megváltoztatása? + No comment provided by engineer. + + + Change lock mode + Zárolási mód megváltoztatása + authentication reason + + + Change member role? + Tag szerepkörének megváltoztatása? + No comment provided by engineer. + + + Change passcode + Jelkód megváltoztatása + authentication reason + + + Change receiving address + A fogadó cím megváltoztatása + No comment provided by engineer. + + + Change receiving address? + Megváltoztatja a fogadó címet? + No comment provided by engineer. + + + Change role + Szerepkör megváltoztatása + No comment provided by engineer. + + + Change self-destruct mode + Önmegsemmisítő mód megváltoztatása + authentication reason + + + Change self-destruct passcode + Önmegsemmisító jelkód megváltoztatása + authentication reason + set passcode view + + + Chat archive + Csevegési archívum + No comment provided by engineer. + + + Chat console + Csevegési konzol + No comment provided by engineer. + + + Chat database + Csevegési adatbázis + No comment provided by engineer. + + + Chat database deleted + Csevegési adatbázis törölve + No comment provided by engineer. + + + Chat database imported + Csevegési adatbázis importálva + No comment provided by engineer. + + + Chat is running + A csevegés fut + No comment provided by engineer. + + + Chat is stopped + A csevegés leállt + No comment provided by engineer. + + + Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + A csevegés leállt. Ha már használta ezt az adatbázist egy másik eszközön, úgy visszaállítás szükséges a csevegés megkezdése előtt. + No comment provided by engineer. + + + Chat migrated! + No comment provided by engineer. + + + Chat preferences + Csevegési beállítások + No comment provided by engineer. + + + Chats + Csevegések + No comment provided by engineer. + + + Check server address and try again. + Kiszolgáló címének ellenőrzése és újrapróbálkozás. + No comment provided by engineer. + + + 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. + No comment provided by engineer. + + + Choose file + Fájl kiválasztása + No comment provided by engineer. + + + Choose from library + Választás a könyvtárból + No comment provided by engineer. + + + Clear + Kiürítés + No comment provided by engineer. + + + Clear conversation + Beszélgetés kiürítése + No comment provided by engineer. + + + Clear conversation? + Beszélgetés kiürítése? + No comment provided by engineer. + + + Clear private notes? + Privát jegyzetek törlése? + No comment provided by engineer. + + + Clear verification + Hitelesítés törlése + No comment provided by engineer. + + + Colors + Színek + No comment provided by engineer. + + + Compare file + Fájl összehasonlítás + server test step + + + Compare security codes with your contacts. + Biztonsági kódok összehasonlítása az ismerősökkel. + No comment provided by engineer. + + + Configure ICE servers + ICE kiszolgálók beállítása + No comment provided by engineer. + + + Confirm + Megerősítés + No comment provided by engineer. + + + Confirm Passcode + Jelkód megerősítése + No comment provided by engineer. + + + Confirm database upgrades + Adatbázis frissítés megerősítése + No comment provided by engineer. + + + Confirm network settings + No comment provided by engineer. + + + Confirm new passphrase… + Új jelmondat megerősítése… + No comment provided by engineer. + + + Confirm password + Jelszó megerősítése + No comment provided by engineer. + + + Confirm that you remember database passphrase to migrate it. + No comment provided by engineer. + + + Confirm upload + No comment provided by engineer. + + + Connect + Kapcsolódás + server test step + + + Connect automatically + Kapcsolódás automatikusan + No comment provided by engineer. + + + Connect incognito + Inkognítóban csatlakozva + No comment provided by engineer. + + + Connect to desktop + Kapcsolódás számítógéphez + No comment provided by engineer. + + + Connect to yourself? + Kapcsolódás saját magához? + No comment provided by engineer. + + + Connect to yourself? +This is your own SimpleX address! + Kapcsolódás saját magához? +Ez a SimpleX azonosítója! + No comment provided by engineer. + + + Connect to yourself? +This is your own one-time link! + Kapcsolódás saját magához? +Ez az egyszer használatos hivatkozása! + No comment provided by engineer. + + + Connect via contact address + Kapcsolódás ismerős azonosítója által + No comment provided by engineer. + + + Connect via link + Kapcsolódás egy hivatkozáson keresztül + No comment provided by engineer. + + + Connect via one-time link + Kapcsolódás egyszer használatos hivatkozáson keresztül + No comment provided by engineer. + + + Connect with %@ + Kapcsolódás ezzel: %@ + No comment provided by engineer. + + + Connected desktop + Csatlakoztatott számítógép + No comment provided by engineer. + + + Connected to desktop + Csatlakozva a számítógéphez + No comment provided by engineer. + + + Connecting to server… + Kapcsolódás a kiszolgálóhoz… + No comment provided by engineer. + + + Connecting to server… (error: %@) + Kapcsolódás a kiszolgálóhoz... (hiba: %@) + No comment provided by engineer. + + + Connecting to desktop + Kapcsolódás a számítógéphez + No comment provided by engineer. + + + Connection + Kapcsolat + No comment provided by engineer. + + + Connection error + Kapcsolódási hiba + No comment provided by engineer. + + + Connection error (AUTH) + Kapcsolódási hiba (AUTH) + No comment provided by engineer. + + + Connection request sent! + Kapcsolódási kérés elküldve! + No comment provided by engineer. + + + Connection terminated + Kapcsolat megszakítva + No comment provided by engineer. + + + Connection timeout + Kapcsolat időtúllépés + No comment provided by engineer. + + + Contact allows + Ismerős engedélyezi + No comment provided by engineer. + + + Contact already exists + Létező ismerős + No comment provided by engineer. + + + Contact hidden: + Ismerős elrejtve: + notification + + + Contact is connected + Ismerős csatlakozott + notification + + + Contact is not connected yet! + Az ismerős még nem csatlakozott! + No comment provided by engineer. + + + Contact name + Ismerős neve + No comment provided by engineer. + + + Contact preferences + Ismerős beállításai + No comment provided by engineer. + + + Contacts + Ismerősök + No comment provided by engineer. + + + Contacts can mark messages for deletion; you will be able to view them. + Az ismerősök törlésre jelölhetnek üzeneteket ; megtekintheti őket. + No comment provided by engineer. + + + Continue + Folytatás + No comment provided by engineer. + + + Copy + Másolás + chat item action + + + Core version: v%@ + Alapverziószám: v%@ + No comment provided by engineer. + + + Correct name to %@? + Név javítása erre: %@? + No comment provided by engineer. + + + Create + Létrehozás + No comment provided by engineer. + + + Create SimpleX address + SimpleX azonosító létrehozása + No comment provided by engineer. + + + Create a group using a random profile. + Csoport létrehozása véletlenszerűen létrehozott profillal. + No comment provided by engineer. + + + Create an address to let people connect with you. + Azonosító létrehozása, hogy az emberek kapcsolatba léphessenek önnel. + No comment provided by engineer. + + + Create file + Fájl létrehozása + server test step + + + Create group + Csoport létrehozása + No comment provided by engineer. + + + Create group link + Csoportos hivatkozás létrehozása + No comment provided by engineer. + + + Create link + Hivatkozás létrehozása + No comment provided by engineer. + + + Create new profile in [desktop app](https://simplex.chat/downloads/). 💻 + Új profil létrehozása az [asztali kliensben](https://simplex.chat/downloads/). 💻 + No comment provided by engineer. + + + Create profile + Profil létrehozása + No comment provided by engineer. + + + Create queue + Várólista létrehozása + server test step + + + Create secret group + Titkos csoport létrehozása + No comment provided by engineer. + + + Create your profile + Saját profil létrehozása + No comment provided by engineer. + + + Created at + Létrehozva ekkor + No comment provided by engineer. + + + Created at: %@ + Létrehozva ekkor: %@ + copied message info + + + Created on %@ + Létrehozva %@ + No comment provided by engineer. + + + Creating archive link + No comment provided by engineer. + + + Creating link… + Hivatkozás létrehozása… + No comment provided by engineer. + + + Current Passcode + Jelenlegi jelkód + No comment provided by engineer. + + + Current passphrase… + Jelenlegi jelmondat… + No comment provided by engineer. + + + Currently maximum supported file size is %@. + Jelenleg a maximális támogatott fájlméret %@. + No comment provided by engineer. + + + Custom time + Személyreszabott idő + No comment provided by engineer. + + + Dark + Sötét + No comment provided by engineer. + + + Database ID + Adatbázis ID + No comment provided by engineer. + + + Database ID: %d + Adatbázis azonosító: %d + copied message info + + + Database IDs and Transport isolation option. + Adatbázis azonosítók és átviteli izolációs beállítások. + No comment provided by engineer. + + + Database downgrade + Visszatérés a korábbi adatbázis verzióra + No comment provided by engineer. + + + Database encrypted! + Adatbázis titkosítva! + No comment provided by engineer. + + + Database encryption passphrase will be updated and stored in the keychain. + + Az adatbázis titkosítási jelmondata frissül és tárolódik a kulcstárolóban. + + No comment provided by engineer. + + + Database encryption passphrase will be updated. + + Adatbázis titkosítási jelmondat frissítve lesz. + + No comment provided by engineer. + + + Database error + Adatbázis hiba + 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, megváltoztatható. + No comment provided by engineer. + + + Database is encrypted using a random passphrase. Please change it before exporting. + Az adatbázis egy véletlenszerű jelmondattal van titkosítva. Exportálás előtti módosítás szükséges. + No comment provided by engineer. + + + Database passphrase + Adatbázis jelmondat + No comment provided by engineer. + + + Database passphrase & export + Adatbázis jelmondat és exportálás + No comment provided by engineer. + + + Database passphrase is different from saved in the keychain. + Az adatbázis jelmondata eltér a kulcstárlóban mentettől. + No comment provided by engineer. + + + Database passphrase is required to open chat. + Adatbázis jelmondat szükséges a csevegés megnyitásához. + No comment provided by engineer. + + + Database upgrade + Adatbázis fejlesztése + No comment provided by engineer. + + + Database will be encrypted and the passphrase stored in the keychain. + + Az adatbázis titkosítva lesz, a jelmondat pedig a kulcstárolóban lesz tárolva. + + No comment provided by engineer. + + + Database will be encrypted. + + Az adatbázis titkosításra kerül. + + No comment provided by engineer. + + + Database will be migrated when the app restarts + Az adatbázis az alkalmazás újraindításakor migrálásra kerül + No comment provided by engineer. + + + Decentralized + Decentralizált + No comment provided by engineer. + + + Decryption error + Titkosítás visszafejtési hiba + message decrypt error item + + + Delete + Törlés + chat item action + + + Delete %lld messages? + Töröl %lld üzenetet? + No comment provided by engineer. + + + Delete Contact + Ismerős törlése + No comment provided by engineer. + + + Delete address + Azonosító törlése + No comment provided by engineer. + + + Delete address? + Azonosító törlése? + No comment provided by engineer. + + + Delete after + Törlés miután + No comment provided by engineer. + + + Delete all files + Minden fájl törlése + No comment provided by engineer. + + + Delete and notify contact + Törlés és ismerős értesítése + No comment provided by engineer. + + + Delete archive + Archívum törlése + No comment provided by engineer. + + + Delete chat archive? + Csevegési archívum törlése? + No comment provided by engineer. + + + Delete chat profile + Csevegési profil törlése + No comment provided by engineer. + + + Delete chat profile? + Csevegési profil törlése? + No comment provided by engineer. + + + Delete connection + Kapcsolat törlése + No comment provided by engineer. + + + Delete contact + Ismerős törlése + No comment provided by engineer. + + + Delete contact? +This cannot be undone! + Ismerős törlése? +Ezt nem vonható vissza! + No comment provided by engineer. + + + Delete database + Adatbázis törlése + No comment provided by engineer. + + + Delete database from this device + No comment provided by engineer. + + + Delete file + Fájl törlése + server test step + + + Delete files and media? + Fájlok és a médiatartalmak törlése? + No comment provided by engineer. + + + Delete files for all chat profiles + Fájlok törlése minden csevegési profilból + No comment provided by engineer. + + + Delete for everyone + Törlés mindenkinél + chat feature + + + Delete for me + Törlés nálam + No comment provided by engineer. + + + Delete group + Csoport törlése + No comment provided by engineer. + + + Delete group? + Csoport törlése? + No comment provided by engineer. + + + Delete invitation + Meghívó törlése + No comment provided by engineer. + + + Delete link + Hivatkozás törlése + No comment provided by engineer. + + + Delete link? + Hivatkozás törlése? + No comment provided by engineer. + + + Delete member message? + Csoporttag üzenet törlése? + No comment provided by engineer. + + + Delete message? + Üzenet törlése? + No comment provided by engineer. + + + Delete messages + Üzenetek törlése + No comment provided by engineer. + + + Delete messages after + Üzenetek törlése miután + No comment provided by engineer. + + + Delete old database + Régi adatbázis törlése + No comment provided by engineer. + + + Delete old database? + Régi adatbázis törlése? + No comment provided by engineer. + + + Delete pending connection + Függőben lévő kapcsolat törlése + No comment provided by engineer. + + + Delete pending connection? + Függő kapcsolatfelvételi kérések törlése? + No comment provided by engineer. + + + Delete profile + Profil törlése + No comment provided by engineer. + + + Delete queue + Várólista törlése + server test step + + + Delete user profile? + Felhasználói profil törlése? + No comment provided by engineer. + + + Deleted at + Törölve ekkor + No comment provided by engineer. + + + Deleted at: %@ + Törölve ekkor: %@ + copied message info + + + Delivery + Kézbesítés + No comment provided by engineer. + + + Delivery receipts are disabled! + Kézbesítési igazolások kikapcsolva! + No comment provided by engineer. + + + Delivery receipts! + Kézbesítési igazolások! + No comment provided by engineer. + + + Description + Leírás + No comment provided by engineer. + + + Desktop address + Számítógép azonosítója + No comment provided by engineer. + + + Desktop app version %@ is not compatible with this app. + Az asztali kliens verziója %@ nem kompatibilis ezzel az alkalmazással. + No comment provided by engineer. + + + Desktop devices + Számítógépek + No comment provided by engineer. + + + Develop + Fejlesztés + No comment provided by engineer. + + + Developer tools + Fejlesztői eszközök + No comment provided by engineer. + + + Device + Eszköz + No comment provided by engineer. + + + Device authentication is disabled. Turning off SimpleX Lock. + Eszközhitelesítés kikapcsolva. SimpleX zárolás kikapcsolása. + No comment provided by engineer. + + + Device authentication is not enabled. You can turn on SimpleX Lock via Settings, once you enable device authentication. + Eszközhitelesítés nem engedélyezett.A SimpleX zárolás bekapcsolható a Beállításokon keresztül, miután az eszköz hitelesítés engedélyezésre került. + No comment provided by engineer. + + + Different names, avatars and transport isolation. + Különböző nevek, avatarok és átviteli izoláció. + No comment provided by engineer. + + + Direct messages + Közvetlen üzenetek + chat feature + + + Direct messages between members are prohibited in this group. + Ebben a csoportban tiltott a tagok közötti közvetlen üzenetek küldése. + No comment provided by engineer. + + + Disable (keep overrides) + Letiltás (felülírások megtartásával) + No comment provided by engineer. + + + Disable SimpleX Lock + SimpleX zárolás kikapcsolása + authentication reason + + + Disable for all + Letiltás mindenki számára + No comment provided by engineer. + + + Disappearing message + Eltűnő üzenet + No comment provided by engineer. + + + Disappearing messages + Eltűnő üzenetek + chat feature + + + Disappearing messages are prohibited in this chat. + Az eltűnő üzenetek le vannak tiltva ebben a csevegésben. + No comment provided by engineer. + + + Disappearing messages are prohibited in this group. + Az eltűnő üzenetek küldése le van tiltva ebben a csoportban. + No comment provided by engineer. + + + Disappears at + Eltűnik ekkor + No comment provided by engineer. + + + Disappears at: %@ + Eltűnik ekkor: %@ + copied message info + + + Disconnect + Kapcsolat bontása + server test step + + + Disconnect desktop? + Számítógép leválasztása? + No comment provided by engineer. + + + Discover and join groups + Helyi csoportok felfedezése és csatlakozás + No comment provided by engineer. + + + Discover via local network + Felfedezés helyi hálózaton keresztül + No comment provided by engineer. + + + Do NOT use SimpleX for emergency calls. + NE használja a SimpleX-et segélyhívásokhoz. + No comment provided by engineer. + + + Do it later + Későbbre halaszt + No comment provided by engineer. + + + Do not send history to new members. + Ne küldjön előzményeket új tagok részére. + No comment provided by engineer. + + + Don't create address + Ne hozzon létre azonosítót + No comment provided by engineer. + + + Don't enable + Ne engedélyezze + No comment provided by engineer. + + + Don't show again + Ne mutasd újra + No comment provided by engineer. + + + Downgrade and open chat + Visszatérés a korábbi verzióra és a csevegés megnyitása + No comment provided by engineer. + + + Download failed + No comment provided by engineer. + + + Download file + Fájl letöltése + server test step + + + Downloading archive + No comment provided by engineer. + + + Downloading link details + No comment provided by engineer. + + + Duplicate display name! + Duplikált megjelenítési név! + No comment provided by engineer. + + + Duration + Időtartam + No comment provided by engineer. + + + Edit + Szerkesztés + chat item action + + + Edit group profile + A csoport profiljának szerkesztése + No comment provided by engineer. + + + Enable + Engedélyezés + No comment provided by engineer. + + + Enable (keep overrides) + Engedélyezés (felülírások megtartásával) + No comment provided by engineer. + + + Enable SimpleX Lock + SimpleX zárolás engedélyezése + authentication reason + + + Enable TCP keep-alive + TCP életben tartásának engedélyezése + No comment provided by engineer. + + + Enable automatic message deletion? + Automatikus üzenet törlés engedélyezése? + No comment provided by engineer. + + + Enable camera access + Kamera hozzáférés engedélyezése + No comment provided by engineer. + + + Enable for all + Engedélyezés mindenki részére + No comment provided by engineer. + + + Enable in direct chats (BETA)! + No comment provided by engineer. + + + Enable instant notifications? + Azonnali értesítések engedélyezése? + No comment provided by engineer. + + + Enable lock + Zárolás engedélyezése + No comment provided by engineer. + + + Enable notifications + Értesítések engedélyezése + No comment provided by engineer. + + + Enable periodic notifications? + Időszakos értesítések engedélyezése? + No comment provided by engineer. + + + Enable self-destruct + Önmegsemmisítés engedélyezése + No comment provided by engineer. + + + Enable self-destruct passcode + Önmegsemmisítő jelkód engedélyezése + set passcode view + + + Encrypt + Titkosít + No comment provided by engineer. + + + Encrypt database? + Adatbázis titkosítása? + No comment provided by engineer. + + + Encrypt local files + Helyi fájlok titkosítása + No comment provided by engineer. + + + Encrypt stored files & media + Tárolt fájlok és médiatartalmak titkosítása + No comment provided by engineer. + + + Encrypted database + Titkosított adatbázis + No comment provided by engineer. + + + Encrypted message or another event + Titkosított üzenet vagy más esemény + notification + + + Encrypted message: app is stopped + Titkosított üzenet: az alkalmazás leállt + notification + + + Encrypted message: database error + Titkosított üzenet: adatbázis hiba + notification + + + Encrypted message: database migration error + Titkosított üzenet: adatbázis-migrációs hiba + notification + + + Encrypted message: keychain error + Titkosított üzenet: kulcstároló hiba + notification + + + Encrypted message: no passphrase + Titkosított üzenet: nincs jelmondat + notification + + + Encrypted message: unexpected error + Titkosított üzenet: váratlan hiba + notification + + + Encryption re-negotiation error + Titkosítás újraegyeztetési hiba + message decrypt error item + + + Encryption re-negotiation failed. + Titkosítás újraegyeztetése sikertelen. + No comment provided by engineer. + + + Enter Passcode + Jelkód megadása + No comment provided by engineer. + + + Enter correct passphrase. + Helyes jelmondat bevitele. + No comment provided by engineer. + + + Enter group name… + Csoportnév megadása… + No comment provided by engineer. + + + Enter passphrase + No comment provided by engineer. + + + Enter passphrase… + Jelmondat megadása… + No comment provided by engineer. + + + Enter password above to show! + Jelszó megadása a megjelenítéshez! + No comment provided by engineer. + + + Enter server manually + Kiszolgáló megadása kézzel + No comment provided by engineer. + + + Enter this device name… + Eszköznév megadása… + No comment provided by engineer. + + + Enter welcome message… + Üdvözlő üzenetet megadása… + placeholder + + + Enter welcome message… (optional) + Üdvözlő üzenetet megadása… (opcionális) + placeholder + + + Enter your name… + Adja meg nevét… + No comment provided by engineer. + + + Error + Hiba + No comment provided by engineer. + + + Error aborting address change + Hiba az azonosító megváltoztatásának megszakításakor + No comment provided by engineer. + + + Error accepting contact request + Hiba történt a kapcsolatfelvételi kérelem elfogadásakor + No comment provided by engineer. + + + Error accessing database file + Hiba az adatbázisfájl elérésekor + No comment provided by engineer. + + + Error adding member(s) + Hiba a tag(-ok) hozzáadásakor + No comment provided by engineer. + + + Error allowing contact PQ encryption + No comment provided by engineer. + + + Error changing address + Hiba az azonosító megváltoztatásakor + No comment provided by engineer. + + + Error changing role + Hiba a szerepkör megváltoztatásakor + No comment provided by engineer. + + + Error changing setting + Hiba a beállítás megváltoztatásakor + No comment provided by engineer. + + + Error creating address + Hiba az azonosító létrehozásakor + No comment provided by engineer. + + + Error creating group + Hiba a csoport létrehozásakor + No comment provided by engineer. + + + Error creating group link + Hiba a csoport hivatkozásának létrehozásakor + No comment provided by engineer. + + + Error creating member contact + Hiba az ismerőssel történő kapcsolat létrehozásában + No comment provided by engineer. + + + Error creating message + Hiba az üzenet létrehozásakor + No comment provided by engineer. + + + Error creating profile! + Hiba a profil létrehozásakor! + No comment provided by engineer. + + + Error decrypting file + Hiba a fájl visszafejtésekor + No comment provided by engineer. + + + Error deleting chat database + Hiba a csevegési adatbázis törlésekor + No comment provided by engineer. + + + Error deleting chat! + Hiba a csevegés törlésekor! + No comment provided by engineer. + + + Error deleting connection + Hiba a kapcsolat törlésekor + No comment provided by engineer. + + + Error deleting contact + Hiba az ismerős törlésekor + No comment provided by engineer. + + + Error deleting database + Hiba az adatbázis törlésekor + No comment provided by engineer. + + + Error deleting old database + Hiba a régi adatbázis törlésekor + No comment provided by engineer. + + + 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 + No comment provided by engineer. + + + Error downloading the archive + No comment provided by engineer. + + + Error enabling delivery receipts! + Hiba a kézbesítési jelentések engedélyezésekor! + No comment provided by engineer. + + + Error enabling notifications + Hiba az értesítések engedélyezésekor + No comment provided by engineer. + + + Error encrypting database + Hiba az adatbázis titkosításakor + No comment provided by engineer. + + + Error exporting chat database + Hiba a csevegési adatbázis exportálásakor + No comment provided by engineer. + + + Error importing chat database + Hiba a csevegési adatbázis importálásakor + No comment provided by engineer. + + + Error joining group + Hiba a csoporthoz való csatlakozáskor + No comment provided by engineer. + + + Error loading %@ servers + Hiba a %@ kiszolgálók betöltésekor + No comment provided by engineer. + + + Error opening chat + Hiba a csevegés megnyitásakor + No comment provided by engineer. + + + Error receiving file + Hiba a fájl fogadásakor + No comment provided by engineer. + + + Error removing member + Hiba a tag eltávolításakor + No comment provided by engineer. + + + Error saving %@ servers + Hiba történt a %@ kiszolgálók mentése közben + No comment provided by engineer. + + + Error saving ICE servers + Hiba az ICE kiszolgálók mentésekor + No comment provided by engineer. + + + Error saving group profile + Hiba a csoport profil mentésekor + No comment provided by engineer. + + + Error saving passcode + Hiba a jelkód mentése közben + No comment provided by engineer. + + + Error saving passphrase to keychain + Hiba a jelmondat kulcstárolóba történő mentésekor + No comment provided by engineer. + + + Error saving settings + when migrating + + + Error saving user password + Hiba a felhasználó jelszavának mentésekor + No comment provided by engineer. + + + Error scanning code: %@ + Hiba a kód beolvasása közben: %@ + No comment provided by engineer. + + + Error sending email + Hiba az e-mail küldésekor + No comment provided by engineer. + + + Error sending member contact invitation + Hiba történt a tag kapcsolatfelvételi meghívójának elküldésekor + No comment provided by engineer. + + + Error sending message + Hiba az üzenet küldésekor + No comment provided by engineer. + + + Error setting delivery receipts! + Hiba történt a kézbesítési igazolások beállításakor! + No comment provided by engineer. + + + Error starting chat + Hiba a csevegés elindításakor + No comment provided by engineer. + + + Error stopping chat + Hiba a csevegés megállításakor + No comment provided by engineer. + + + Error switching profile! + Hiba a profil váltásakor! + No comment provided by engineer. + + + Error synchronizing connection + Hiba a kapcsolat szinkronizálása során + No comment provided by engineer. + + + Error updating group link + Hiba a csoport hivatkozás frissítésekor + No comment provided by engineer. + + + Error updating message + Hiba az üzenet frissítésekor + No comment provided by engineer. + + + Error updating settings + Hiba történt a beállítások frissítésekor + No comment provided by engineer. + + + Error updating user privacy + Hiba a felhasználói beállítások frissítésekor + No comment provided by engineer. + + + Error uploading the archive + No comment provided by engineer. + + + Error verifying passphrase: + No comment provided by engineer. + + + Error: + Hiba: + No comment provided by engineer. + + + Error: %@ + Hiba: %@ + No comment provided by engineer. + + + Error: URL is invalid + Hiba: az URL érvénytelen + No comment provided by engineer. + + + Error: no database file + Hiba: nincs adatbázis fájl + No comment provided by engineer. + + + Even when disabled in the conversation. + Akkor is, ha le van tiltva a beszélgetésben. + No comment provided by engineer. + + + Exit without saving + Kilépés mentés nélkül + No comment provided by engineer. + + + Expand + Kibontás + chat item action + + + Export database + Adatbázis exportálása + No comment provided by engineer. + + + Export error: + Exportálási hiba: + No comment provided by engineer. + + + Exported database archive. + Exportált adatbázis-archívum. + No comment provided by engineer. + + + Exported file doesn't exist + No comment provided by engineer. + + + Exporting database archive… + Adatbázis archívum exportálása… + No comment provided by engineer. + + + Failed to remove passphrase + Nem sikerült eltávolítani a jelmondatot + No comment provided by engineer. + + + Fast and no wait until the sender is online! + Gyors és nem kell várni, amíg a feladó online lesz! + No comment provided by engineer. + + + Faster joining and more reliable messages. + Gyorsabb csatlakozás és megbízhatóbb üzenet kézbesítés. + No comment provided by engineer. + + + Favorite + Kedvenc + No comment provided by engineer. + + + File will be deleted from servers. + A fájl törölve lesz a kiszolgálóról. + No comment provided by engineer. + + + File will be received when your contact completes uploading it. + A fájl akkor érkezik meg, amikor ismerőse befejezte annak feltöltést. + No comment provided by engineer. + + + File will be received when your contact is online, please wait or check later! + A fájl akkor érkezik meg, amint ismerőse online lesz, várjon, vagy ellenőrizze később! + No comment provided by engineer. + + + File: %@ + Fájl: %@ + No comment provided by engineer. + + + Files & media + Fájlok és média + No comment provided by engineer. + + + Files and media + Fájlok és médiatartalom + chat feature + + + Files and media are prohibited in this group. + A fájlok- és a médiatartalom küldése le van tiltva ebben a csoportban. + No comment provided by engineer. + + + Files and media prohibited! + A fájlok- és a médiatartalom küldése le van tiltva! + No comment provided by engineer. + + + Filter unread and favorite chats. + Olvasatlan és kedvenc csevegésekre való szűrés. + No comment provided by engineer. + + + Finalize migration + No comment provided by engineer. + + + Finalize migration on another device. + No comment provided by engineer. + + + Finally, we have them! 🚀 + Végre, megvannak! 🚀 + No comment provided by engineer. + + + Find chats faster + Csevegési üzenetek gyorsabb megtalálása + No comment provided by engineer. + + + Fix + Javítás + No comment provided by engineer. + + + Fix connection + Kapcsolat javítása + No comment provided by engineer. + + + Fix connection? + Kapcsolat javítása? + No comment provided by engineer. + + + Fix encryption after restoring backups. + Titkosítás javítása az adatmentések helyreállítása után. + No comment provided by engineer. + + + Fix not supported by contact + Ismerős általi javítás nem támogatott + No comment provided by engineer. + + + Fix not supported by group member + Csoporttag általi javítás nem támogatott + No comment provided by engineer. + + + For console + Konzolhoz + No comment provided by engineer. + + + Found desktop + Megtalált számítógép + No comment provided by engineer. + + + French interface + Francia kezelőfelület + No comment provided by engineer. + + + Full link + Teljes hivatkozás + No comment provided by engineer. + + + Full name (optional) + Teljes név (opcionális) + No comment provided by engineer. + + + Full name: + Teljes név: + No comment provided by engineer. + + + Fully decentralized – visible only to members. + Teljesen decentralizált - kizárólag tagok számára látható. + No comment provided by engineer. + + + Fully re-implemented - work in background! + Teljesen újra implementálva - háttérben történő működés! + No comment provided by engineer. + + + Further reduced battery usage + Tovább csökkentett akkumulátor használat + No comment provided by engineer. + + + GIFs and stickers + GIF-ek és matricák + No comment provided by engineer. + + + Group + Csoport + No comment provided by engineer. + + + Group already exists + A csoport már létezik + No comment provided by engineer. + + + Group already exists! + A csoport már létezik! + No comment provided by engineer. + + + Group display name + A csoport megjelenített neve + No comment provided by engineer. + + + Group full name (optional) + Csoport teljes neve (opcionális) + No comment provided by engineer. + + + Group image + Csoportkép + No comment provided by engineer. + + + Group invitation + Csoportos meghívó + No comment provided by engineer. + + + Group invitation expired + A csoport meghívó lejárt + No comment provided by engineer. + + + Group invitation is no longer valid, it was removed by sender. + A csoport meghívó már nem érvényes, el lett távolítva a küldője által. + No comment provided by engineer. + + + Group link + Csoport hivatkozás + No comment provided by engineer. + + + Group links + Csoport hivatkozások + No comment provided by engineer. + + + Group members can add message reactions. + Csoporttagok üzenetreakciókat adhatnak hozzá. + No comment provided by engineer. + + + Group members can irreversibly delete sent messages. (24 hours) + Csoporttagok visszafordíthatatlanul törölhetik az elküldött üzeneteket. (24 óra) + No comment provided by engineer. + + + Group members can send direct messages. + Csoporttagok küldhetnek közvetlen üzeneteket. + No comment provided by engineer. + + + Group members can send disappearing messages. + Csoporttagok küldhetnek eltűnő üzeneteket. + No comment provided by engineer. + + + Group members can send files and media. + Csoporttagok küldhetnek fájlokat és médiatartalmakat. + No comment provided by engineer. + + + Group members can send voice messages. + Csoporttagok küldhetnek hangüzeneteket. + No comment provided by engineer. + + + Group message: + Csoport üzenet: + notification + + + Group moderation + Csoport moderáció + No comment provided by engineer. + + + Group preferences + Csoport beállítások + No comment provided by engineer. + + + Group profile + Csoport profil + No comment provided by engineer. + + + Group profile is stored on members' devices, not on the servers. + A csoport profilja a tagok eszközein tárolódik, nem a kiszolgálókon. + No comment provided by engineer. + + + Group welcome message + Csoport üdvözlő üzenete + No comment provided by engineer. + + + Group will be deleted for all members - this cannot be undone! + Csoport törlésre kerül minden tag számára - ez nem vonható vissza! + No comment provided by engineer. + + + Group will be deleted for you - this cannot be undone! + A csoport törlésre kerül az ön részére - ez nem vonható vissza! + No comment provided by engineer. + + + Help + Segítség + No comment provided by engineer. + + + Hidden + Rejtett + No comment provided by engineer. + + + Hidden chat profiles + Rejtett csevegési profilok + No comment provided by engineer. + + + Hidden profile password + Rejtett profil jelszó + No comment provided by engineer. + + + Hide + Elrejt + chat item action + + + Hide app screen in the recent apps. + Alkalmazás képernyőjének elrejtése a gyakran használt alkalmazások között. + No comment provided by engineer. + + + Hide profile + Profil elrejtése + No comment provided by engineer. + + + Hide: + Elrejt: + No comment provided by engineer. + + + History + Előzmények + No comment provided by engineer. + + + History is not sent to new members. + Az előzmények nem kerülnek elküldésre új tagok részére. + No comment provided by engineer. + + + How SimpleX works + Hogyan működik a SimpleX + No comment provided by engineer. + + + How it works + Hogyan működik + No comment provided by engineer. + + + How to + Hogyan + No comment provided by engineer. + + + How to use it + Hogyan használja + No comment provided by engineer. + + + How to use your servers + Kiszolgálók használata + No comment provided by engineer. + + + Hungarian interface + No comment provided by engineer. + + + ICE servers (one per line) + ICE-kiszolgálók (soronként egy) + No comment provided by engineer. + + + If you can't meet in person, show QR code in a video call, or share the link. + Ha nem tud személyesen találkozni, mutassa meg a QR-kódot egy videohívás során, vagy ossza meg a hivatkozást. + No comment provided by engineer. + + + If you enter this passcode when opening the app, all app data will be irreversibly removed! + Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat visszafordíthatatlanul törlődik! + No comment provided by engineer. + + + If you enter your self-destruct passcode while opening the app: + Ha az alkalmazás megnyitásakor az önmegsemmisítő jelkódot megadásra kerül: + 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 a ** Csináld később** elemre (az alkalmazás újraindításakor felajánlásra kerül az adatbázis áttelepítése). + No comment provided by engineer. + + + Ignore + Figyelmen kívül hagyás + No comment provided by engineer. + + + Image will be received when your contact completes uploading it. + A kép akkor érkezik meg, amikor ismerőse befejezte annak feltöltését. + No comment provided by engineer. + + + Image will be received when your contact is online, please wait or check later! + A kép akkor érkezik meg, amikor ismerős elérhető lesz, várjon vagy ellenőrizze később! + No comment provided by engineer. + + + Immediately + Azonnal + No comment provided by engineer. + + + Immune to spam and abuse + Spam és visszaélések elleni védelem + No comment provided by engineer. + + + Import + Importálás + No comment provided by engineer. + + + Import chat database? + Csevegési adatbázis importálása? + No comment provided by engineer. + + + Import database + Adatbázis importálása + No comment provided by engineer. + + + Import failed + No comment provided by engineer. + + + Importing archive + No comment provided by engineer. + + + Improved message delivery + Továbbfejlesztett üzenetküldés + No comment provided by engineer. + + + Improved privacy and security + Fejlesztett adatvédelem és biztonság + No comment provided by engineer. + + + Improved server configuration + Javított kiszolgáló konfiguráció + No comment provided by engineer. + + + In order to continue, chat should be stopped. + No comment provided by engineer. + + + In reply to + Válasz neki + No comment provided by engineer. + + + Incognito + Inkognitó + No comment provided by engineer. + + + Incognito groups + Inkognitó csoportok + No comment provided by engineer. + + + Incognito mode + Inkognitó mód + No comment provided by engineer. + + + Incognito mode protects your privacy by using a new random profile for each contact. + Az inkognitómód védi személyes adatait azáltal, hogy minden ismerőshöz új véletlenszerű profilt használ. + No comment provided by engineer. + + + Incoming audio call + Bejövő hanghívás + notification + + + Incoming call + Bejövő hívás + notification + + + Incoming video call + Bejövő videóhívás + notification + + + Incompatible database version + Nem kompatibilis adatbázis verzió + No comment provided by engineer. + + + Incompatible version + Nem kompatibilis verzió + No comment provided by engineer. + + + Incorrect passcode + Téves jelkód + PIN entry + + + Incorrect security code! + Helytelen biztonsági kód! + No comment provided by engineer. + + + Info + Információ + chat item action + + + Initial role + Kezdeti szerepkör + No comment provided by engineer. + + + Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat) + A [SimpleX Chat terminálhoz] telepítése (https://github.com/simplex-chat/simplex-chat) + No comment provided by engineer. + + + Instant push notifications will be hidden! + + Az azonnali push értesítések elrejtésre kerülnek! + + No comment provided by engineer. + + + Instantly + Azonnal + No comment provided by engineer. + + + Interface + Felület + No comment provided by engineer. + + + Invalid QR code + Érvénytelen QR-kód + No comment provided by engineer. + + + Invalid connection link + Érvénytelen kapcsolati hivatkozás + No comment provided by engineer. + + + Invalid display name! + Érvénytelen megjelenítendő felhaszálónév! + No comment provided by engineer. + + + Invalid link + Érvénytelen hivatkozás + No comment provided by engineer. + + + Invalid migration confirmation + No comment provided by engineer. + + + Invalid name! + Érvénytelen név! + No comment provided by engineer. + + + Invalid response + Érvénytelen válasz + No comment provided by engineer. + + + Invalid server address! + Érvénytelen kiszolgálócím! + No comment provided by engineer. + + + Invalid status + Érvénytelen állapot + item status text + + + Invitation expired! + A meghívó lejárt! + No comment provided by engineer. + + + Invite friends + Barátok meghívása + No comment provided by engineer. + + + Invite members + Tagok meghívása + No comment provided by engineer. + + + Invite to group + Meghívás a csoportba + No comment provided by engineer. + + + Irreversible message deletion + Visszafordíthatatlan üzenettörlés + No comment provided by engineer. + + + Irreversible message deletion is prohibited in this chat. + Ebben a csevegésben az üzenetek visszafordíthatatlan törlése le van tiltva. + No comment provided by engineer. + + + Irreversible message deletion is prohibited in this group. + Ebben a csoportban az üzenetek visszafordíthatatlan törlése le van tiltva. + No comment provided by engineer. + + + It allows having many anonymous connections without any shared data between them in a single chat profile. + Lehetővé teszi, hogy egyetlen csevegőprofilon belül több anonim kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük. + No comment provided by engineer. + + + It can happen when you or your connection used the old database backup. + Ez akkor fordulhat elő, ha ön vagy a kapcsolata régi adatbázis biztonsági mentést használt. + No comment provided by engineer. + + + It can happen when: +1. The messages expired in the sending client after 2 days or on the server after 30 days. +2. Message decryption failed, because you or your contact used old database backup. +3. The connection was compromised. + Ez akkor fordulhat elő, ha: +1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak. +2. Az üzenet visszafejtése sikertelen volt, mert vagy az ismerőse régebbi adatbázis biztonsági mentést használt. +3. A kapcsolat sérült. + No comment provided by engineer. + + + It seems like you are already connected via this link. If it is not the case, there was an error (%@). + Úgy tűnik, már csatlakozott ezen a hivatkozáson keresztül. Ha ez nem így van, akkor hiba történt (%@). + No comment provided by engineer. + + + Italian interface + Olasz kezelőfelület + No comment provided by engineer. + + + Japanese interface + Japán kezelőfelület + No comment provided by engineer. + + + Join + Csatlakozás + No comment provided by engineer. + + + Join group + Csatlakozás csoporthoz + No comment provided by engineer. + + + Join group conversations + Csatlakozás csoportos beszélgetésekhez + No comment provided by engineer. + + + Join group? + Csatlakozik a csoporthoz? + No comment provided by engineer. + + + Join incognito + Csatlakozás inkognitóban + No comment provided by engineer. + + + Join with current profile + Csatlakozás a jelenlegi profillal + No comment provided by engineer. + + + Join your group? +This is your link for group %@! + Csatlakozik a csoportjához? +Ez az ön hivatkozása a(z) %@ csoporthoz! + No comment provided by engineer. + + + Joining group + Csatlakozás a csoporthoz + No comment provided by engineer. + + + Keep + Megtart + No comment provided by engineer. + + + Keep the app open to use it from desktop + A számítógépről való használathoz tartsd nyitva az alkalmazást + No comment provided by engineer. + + + Keep unused invitation? + Fel nem használt meghívó megtartása? + No comment provided by engineer. + + + Keep your connections + Kapcsolatok megtartása + No comment provided by engineer. + + + KeyChain error + Kulcstároló hiba + No comment provided by engineer. + + + Keychain error + Kulcstároló hiba + No comment provided by engineer. + + + LIVE + ÉLŐ + No comment provided by engineer. + + + Large file! + Nagy fájl! + No comment provided by engineer. + + + Learn more + Tudjon meg többet + No comment provided by engineer. + + + Leave + Elhagy + No comment provided by engineer. + + + Leave group + Csoport elhagyása + No comment provided by engineer. + + + Leave group? + Csoport elhagyása? + No comment provided by engineer. + + + Let's talk in SimpleX Chat + Beszélgessünk a SimpleX Chat-ben + email subject + + + Light + Világos + No comment provided by engineer. + + + Limitations + Korlátozások + No comment provided by engineer. + + + Link mobile and desktop apps! 🔗 + Társítsa össze a mobil és az asztali alkalmazásokat! 🔗 + No comment provided by engineer. + + + Linked desktop options + Összekapcsolt számítógép beállítások + No comment provided by engineer. + + + Linked desktops + Összekapcsolt számítógépek + No comment provided by engineer. + + + Live message! + Élő üzenet! + No comment provided by engineer. + + + Live messages + Élő üzenetek + No comment provided by engineer. + + + Local + Helyi + No comment provided by engineer. + + + Local name + Helyi név + No comment provided by engineer. + + + Local profile data only + Csak helyi profiladatok + No comment provided by engineer. + + + Lock after + Zárolás miután + No comment provided by engineer. + + + Lock mode + Zárolási mód + No comment provided by engineer. + + + Make a private connection + Privát kapcsolat létrehozása + No comment provided by engineer. + + + Make one message disappear + Egy üzenet eltüntetése + No comment provided by engineer. + + + Make profile private! + Tegye priváttá profilját! + No comment provided by engineer. + + + Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@). + Győződjön meg arról, hogy a %@ szervercímek megfelelő formátumúak, sorszeparáltak és nem duplikáltak (%@). + No comment provided by engineer. + + + Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated. + Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nem duplikáltak. + 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-nek nincsenek felhasználói 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 + No comment provided by engineer. + + + Mark read + Megjelölés olvasottként + No comment provided by engineer. + + + Mark verified + Ellenőrzöttként jelölve + No comment provided by engineer. + + + Markdown in messages + Markdown az üzenetekben + No comment provided by engineer. + + + Max 30 seconds, received instantly. + Max. 30 másodperc, azonnal érkezett. + No comment provided by engineer. + + + Member + Tag + No comment provided by engineer. + + + Member role will be changed to "%@". All group members will be notified. + A tag szerepköre meg fog változni erre: "%@". A csoport minden tagja értesítést kap róla. + No comment provided by engineer. + + + Member role will be changed to "%@". The member will receive a new invitation. + A tag szerepköre meg fog változni erre: "%@". A tag új meghívást fog kapni. + No comment provided by engineer. + + + Member will be removed from group - this cannot be undone! + A tag eltávolítása a csoportból - ez nem vonható vissza! + No comment provided by engineer. + + + Message delivery error + Üzenetkézbesítési hiba + item status text + + + Message delivery receipts! + Üzenetkézbesítési bizonylatok! + No comment provided by engineer. + + + Message draft + Üzenetvázlat + No comment provided by engineer. + + + Message reactions + Üzenetreakciók + chat feature + + + Message reactions are prohibited in this chat. + Az üzenetreakciók ebben a csevegésben le vannak tiltva. + No comment provided by engineer. + + + Message reactions are prohibited in this group. + Ebben a csoportban az üzenetreakciók le vannak tiltva. + No comment provided by engineer. + + + Message text + Üzenet szövege + No comment provided by engineer. + + + Message too large + No comment provided by engineer. + + + Messages + Üzenetek + No comment provided by engineer. + + + Messages & files + Üzenetek és fájlok + No comment provided by engineer. + + + Messages from %@ will be shown! + A(z) %@ által írt üzenetek megjelennek! + 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. + No comment provided by engineer. + + + Migrate device + No comment provided by engineer. + + + Migrate from another device + No comment provided by engineer. + + + Migrate here + No comment provided by engineer. + + + Migrate to another device + No comment provided by engineer. + + + Migrate to another device via QR code. + No comment provided by engineer. + + + Migrating + No comment provided by engineer. + + + Migrating database archive… + Adatbázis archívum migrálása… + No comment provided by engineer. + + + Migration complete + No comment provided by engineer. + + + Migration error: + Migrációs hiba: + No comment provided by engineer. + + + Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat). + Sikertelen migráció. Koppintson a **Kihagyás** lehetőségre az aktuális adatbázis használatának folytatásához. Kérjük, jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat). + No comment provided by engineer. + + + Migration is completed + A migráció befejeződött + No comment provided by engineer. + + + Migrations: %@ + Migrációk: %@ + No comment provided by engineer. + + + Moderate + Moderálás + chat item action + + + Moderated at + Moderálva ekkor + No comment provided by engineer. + + + Moderated at: %@ + Moderálva ekkor: %@ + copied message info + + + More improvements are coming soon! + Hamarosan további fejlesztések érkeznek! + No comment provided by engineer. + + + Most likely this connection is deleted. + Valószínűleg ez a kapcsolat törlésre került. + item status description + + + Most likely this contact has deleted the connection with you. + Valószínűleg ez az ismerős törölte önnel a kapcsolatot. + No comment provided by engineer. + + + Multiple chat profiles + Több csevegőprofil + No comment provided by engineer. + + + Mute + Elnémítás + No comment provided by engineer. + + + Muted when inactive! + Némítás, ha inaktív! + No comment provided by engineer. + + + Name + Név + No comment provided by engineer. + + + Network & servers + Hálózat és kiszolgálók + No comment provided by engineer. + + + Network settings + Hálózati beállítások + No comment provided by engineer. + + + Network status + Hálózat állapota + No comment provided by engineer. + + + New Passcode + Új jelkód + No comment provided by engineer. + + + New chat + Új beszélgetés + No comment provided by engineer. + + + New contact request + Új kapcsolattartási kérelem + notification + + + New contact: + Új kapcsolat: + notification + + + New database archive + Új adatbázis-archívum + No comment provided by engineer. + + + New desktop app! + Új asztali alkalmazás! + No comment provided by engineer. + + + New display name + Új megjelenítési név + No comment provided by engineer. + + + New in %@ + Újdonságok a(z) %@ verzióban + No comment provided by engineer. + + + New member role + Új tag szerepköre + No comment provided by engineer. + + + New message + Új üzenet + notification + + + New passphrase… + Új jelmondat… + No comment provided by engineer. + + + No + Nem + No comment provided by engineer. + + + No app password + Nincs alkalmazás jelszó + Authentication unavailable + + + No contacts selected + Nem kerültek ismerősök kiválasztásra + No comment provided by engineer. + + + No contacts to add + Nincs hozzáadandó ismerős + No comment provided by engineer. + + + No delivery information + Nincs kézbesítési információ + No comment provided by engineer. + + + No device token! + Nincs eszköztoken! + No comment provided by engineer. + + + No filtered chats + Nincsenek szűrt csevegések + No comment provided by engineer. + + + Group not found! + Csoport nem található! + No comment provided by engineer. + + + No history + Nincsenek előzmények + No comment provided by engineer. + + + No permission to record voice message + Nincs engedély a hangüzenet rögzítésére + No comment provided by engineer. + + + No received or sent files + Nincsenek fogadott vagy küldött fájlok + No comment provided by engineer. + + + Not compatible! + Nem kompatibilis! + No comment provided by engineer. + + + Notifications + Értesítések + No comment provided by engineer. + + + Notifications are disabled! + Az értesítések le vannak tiltva! + No comment provided by engineer. + + + Now admins can: +- delete members' messages. +- disable members ("observer" role) + Most már az adminok is: +- törölhetik a tagok üzeneteit. +- letilthatnak tagokat ("megfigyelő" szerepkör) + No comment provided by engineer. + + + OK + Rendben + No comment provided by engineer. + + + Off + Ki + No comment provided by engineer. + + + Ok + Rendben + No comment provided by engineer. + + + Old database + Régi adatbázis + No comment provided by engineer. + + + Old database archive + Régi adatbázis archívum + No comment provided by engineer. + + + One-time invitation link + Egyszer használatos meghívó hivatkozás + No comment provided by engineer. + + + Onion hosts will be required for connection. Requires enabling VPN. + A csatlakozáshoz Onion host-okra lesz szükség. VPN engedélyezése szükséges. + No comment provided by engineer. + + + Onion hosts will be used when available. Requires enabling VPN. + Onion host-ok használata, ha azok rendelkezésre állnak. VPN engedélyezése szükséges. + No comment provided by engineer. + + + Onion hosts will not be used. + Onion host-ok 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 a klienseszközök tárolják a felhasználói profilokat, névjegyeket, csoportokat és a **2 rétegű végponttól-végpontig titkosítással** küldött üzeneteket. + No comment provided by engineer. + + + Only group owners can change group preferences. + Csak a csoporttulajdonosok módosíthatják a csoportbeállításokat. + No comment provided by engineer. + + + Only group owners can enable files and media. + Csak a csoporttulajdonosok engedélyezhetik a fájlok- és a médiatartalmak küldését. + No comment provided by engineer. + + + Only group owners can enable voice messages. + Csak a csoporttulajdonosok engedélyezhetik a hangüzenetek küldését. + No comment provided by engineer. + + + Only you can add message reactions. + Csak ön adhat hozzá üzenetreakciókat. + No comment provided by engineer. + + + Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours) + Visszafordíthatatlanul csak ön törölhet üzeneteket (ismerőse csak törlésre jelölheti őket ). (24 óra) + No comment provided by engineer. + + + Only you can make calls. + Csak ön tud hívásokat indítani. + No comment provided by engineer. + + + Only you can send disappearing messages. + Csak ön tud eltűnő üzeneteket küldeni. + No comment provided by engineer. + + + Only you can send voice messages. + Csak ön tud hangüzeneteket küldeni. + No comment provided by engineer. + + + Only your contact can add message reactions. + Csak az ismerős tud üzeneteakciókat adni. + No comment provided by engineer. + + + Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours) + Csak az ismerős tud visszafordíthatatlanul törölni üzeneteket (megjelölheti őket törlésre). (24 óra) + No comment provided by engineer. + + + Only your contact can make calls. + Csak az ismerős tud hívást indítani. + No comment provided by engineer. + + + Only your contact can send disappearing messages. + Csak az ismerős tud eltűnő üzeneteket küldeni. + No comment provided by engineer. + + + Only your contact can send voice messages. + Csak az ismerős tud hangüzeneteket küldeni. + No comment provided by engineer. + + + Open + Megnyitás + No comment provided by engineer. + + + Open Settings + Beállítások megnyitása + No comment provided by engineer. + + + Open chat + Csevegés megnyitása + No comment provided by engineer. + + + Open chat console + Csevegés konzol megnyitása + authentication reason + + + Open group + Csoport megnyitása + No comment provided by engineer. + + + Open migration to another device + authentication reason + + + Open user profiles + Felhasználói profilok megnyitása + authentication reason + + + Open-source protocol and code – anybody can run the servers. + Nyílt forráskódú protokoll és forráskód – bárki üzemeltethet kiszolgálókat. + No comment provided by engineer. + + + Opening app… + Az alkalmazás megnyitása… + No comment provided by engineer. + + + Or paste archive link + No comment provided by engineer. + + + Or scan QR code + Vagy QR-kód beolvasása + No comment provided by engineer. + + + Or securely share this file link + No comment provided by engineer. + + + Or show this code + Vagy mutassa meg ezt a kódot + No comment provided by engineer. + + + PING count + PING számláló + No comment provided by engineer. + + + PING interval + PING időköze + No comment provided by engineer. + + + Passcode + Jelkód + No comment provided by engineer. + + + Passcode changed! + A jelkód megváltozott! + No comment provided by engineer. + + + Passcode entry + Jelkód bevitele + No comment provided by engineer. + + + Passcode not changed! + A jelkód nem változott! + No comment provided by engineer. + + + Passcode set! + A jelkód beállítva! + No comment provided by engineer. + + + Password to show + Jelszó mutatása + No comment provided by engineer. + + + Past member %@ + Korábbi csoport tag %@ + past/unknown group member + + + Paste desktop address + Számítógép azonosítójának beillesztése + No comment provided by engineer. + + + Paste image + Kép beillesztése + No comment provided by engineer. + + + Paste link to connect! + Hivatkozás beillesztése a csatlakozáshoz! + No comment provided by engineer. + + + Paste the link you received + Fogadott hivatkozás beillesztése + No comment provided by engineer. + + + People can connect to you only via the links you share. + Az emberek csak az ön által megosztott hivatkozáson keresztül kapcsolódhatnak. + No comment provided by engineer. + + + Periodically + Rendszeresen + No comment provided by engineer. + + + Permanent decryption error + Végleges visszafejtési hiba + message decrypt error item + + + Picture-in-picture calls + No comment provided by engineer. + + + Please ask your contact to enable sending voice messages. + Ismerős felkérése, hogy engedélyezze a hangüzenetek küldését. + No comment provided by engineer. + + + Please check that you used the correct link or ask your contact to send you another one. + Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg ismerősét, hogy küldjön egy másikat. + No comment provided by engineer. + + + Please check your network connection with %@ and try again. + Kérjük, ellenőrizze hálózati kapcsolatát a(z) %@ segítségével, és próbálja újra. + No comment provided by engineer. + + + Please check yours and your contact preferences. + Ellenőrizze az ön és ismerőse beállításait. + No comment provided by engineer. + + + Please confirm that network settings are correct for this device. + No comment provided by engineer. + + + Please contact developers. +Error: %@ + Lépjen kapcsolatba a fejlesztőkkel. +Hiba: %@ + No comment provided by engineer. + + + Please contact group admin. + Lépjen kapcsolatba a csoport adminnal. + No comment provided by engineer. + + + Please enter correct current passphrase. + Adja meg a helyes aktuális jelmondatát. + No comment provided by engineer. + + + Please enter the previous password after restoring database backup. This action can not be undone. + Előző jelszó megadása az adatbázis biztonsági mentésének visszaállítása után. Ez a művelet nem visszavonható. + No comment provided by engineer. + + + Please remember or store it securely - there is no way to recover a lost passcode! + Jegyezze fel vagy tárolja el biztonságosan - az elveszett jelkódot nem lehet visszaállítani! + No comment provided by engineer. + + + Please report it to the developers. + Jelentse a fejlesztőknek. + No comment provided by engineer. + + + Please restart the app and migrate the database to enable push notifications. + Indítsa újra az alkalmazást az adatbázis-migrációhoz szükséges push értesítések engedélyezéséhez. + No comment provided by engineer. + + + Please store passphrase securely, you will NOT be able to access chat if you lose it. + Tárolja el biztonságosan jelmondát, mert ha elveszti azt, akkor NEM férhet hozzá a csevegéshez. + No comment provided by engineer. + + + Please store passphrase securely, you will NOT be able to change it if you lose it. + Tárolja el biztonságosan jelmondatát, mert ha elveszíti azt, NEM tudja megváltoztatni. + No comment provided by engineer. + + + Polish interface + Lengyel kezelőfelület + No comment provided by engineer. + + + Possibly, certificate fingerprint in server address is incorrect + Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen + server test error + + + Post-quantum E2EE + No comment provided by engineer. + + + Preserve the last message draft, with attachments. + Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt. + No comment provided by engineer. + + + Preset server + Előre beállított kiszolgáló + No comment provided by engineer. + + + Preset server address + Előre beállított kiszolgáló címe + No comment provided by engineer. + + + Preview + Előnézet + No comment provided by engineer. + + + Privacy & security + Adatvédelem és biztonság + No comment provided by engineer. + + + Privacy redefined + Adatvédelem újraértelmezve + No comment provided by engineer. + + + Private filenames + Privát fájl nevek + No comment provided by engineer. + + + Private notes + Privát jegyzetek + name of notes to self + + + Profile and server connections + Profil és kiszolgálókapcsolatok + No comment provided by engineer. + + + Profile image + Profilkép + No comment provided by engineer. + + + Profile name + Profilnév + No comment provided by engineer. + + + Profile name: + Profil neve: + No comment provided by engineer. + + + Profile password + Profiljelszó + No comment provided by engineer. + + + Profile update will be sent to your contacts. + A profilfrissítés elküldésre került az ismerősök számára. + No comment provided by engineer. + + + Prohibit audio/video calls. + Hang- és videóhívások tiltása. + No comment provided by engineer. + + + Prohibit irreversible message deletion. + Az üzenetek véglegesen való törlése le van tiltva. + No comment provided by engineer. + + + Prohibit message reactions. + Üzenetreakciók tiltása. + No comment provided by engineer. + + + Prohibit messages reactions. + Az üzenetreakciók tiltása. + No comment provided by engineer. + + + Prohibit sending direct messages to members. + Közvetlen üzenetek küldésének letiltása tagok részére. + No comment provided by engineer. + + + Prohibit sending disappearing messages. + Eltűnő üzenetek küldésének letiltása. + No comment provided by engineer. + + + Prohibit sending files and media. + Fájlok- és a médiatartalom küldés letiltása. + No comment provided by engineer. + + + Prohibit sending voice messages. + Hangüzenetek küldésének letiltása. + No comment provided by engineer. + + + Protect app screen + App képernyőjének védelme + No comment provided by engineer. + + + Protect your chat profiles with a password! + Csevegési profiljok védelme jelszóval! + No comment provided by engineer. + + + Protocol timeout + Protokoll időtúllépés + No comment provided by engineer. + + + Protocol timeout per KB + Protokoll időkorlát KB-onként + No comment provided by engineer. + + + Push notifications + Push értesítések + No comment provided by engineer. + + + Push server + No comment provided by engineer. + + + Quantum resistant encryption + No comment provided by engineer. + + + Rate the app + Értékelje az alkalmazást + No comment provided by engineer. + + + React… + Reagálj… + chat item menu + + + Read + Olvasd el + No comment provided by engineer. + + + Read more + Tudjon meg többet + No comment provided by engineer. + + + Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). + További információ a [Felhasználói útmutatóban](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address). + No comment provided by engineer. + + + Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + További információ a [Felhasználói útmutatóban](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + No comment provided by engineer. + + + Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends). + További információ a [Felhasználói útmutatóban](https://simplex.chat/docs/guide/readme.html#connect-to-friends). + No comment provided by engineer. + + + Read more in our GitHub repository. + További információ a GitHub tárolónkban. + No comment provided by engineer. + + + Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme). + További információ a [GitHub tárolóban](https://github.com/simplex-chat/simplex-chat#readme). + No comment provided by engineer. + + + Receipts are disabled + Üzenet kézbesítési jelentés letiltva + No comment provided by engineer. + + + Received at + Fogadva ekkor + No comment provided by engineer. + + + Received at: %@ + Fogadva ekkor: %@ + copied message info + + + Received file event + Fogadott fájl esemény + notification + + + Received message + Fogadott üzenet + message info title + + + Receiving address will be changed to a different server. Address change will complete after sender comes online. + A fogadó cím egy másik kiszolgálóra változik. A címváltoztatás a feladó online állapotba kerülése után fejeződik be. + No comment provided by engineer. + + + Receiving file will be stopped. + A fájl fogadása leállt. + No comment provided by engineer. + + + Receiving via + Fogadás a + No comment provided by engineer. + + + Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). + Legutóbbi előzmények és továbbfejlesztett [könyvtárbot] (simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2TxW3dfMfxy 3%23%2F%3Fv%3D1-2% 26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gloncbqjek4gloncbqjek. + No comment provided by engineer. + + + Recipients see updates as you type them. + A címzettek a beírás közben látják a frissítéseket. + No comment provided by engineer. + + + Reconnect all connected servers to force message delivery. It uses additional traffic. + Az összes csatlakoztatott kiszolgáló újrakapcsolása az üzenetek kézbesítésének kikényszerítéséhez. Ez további forgalmat használ. + No comment provided by engineer. + + + Reconnect servers? + Kiszolgálók újracsatlakoztatása? + No comment provided by engineer. + + + Record updated at + A bejegyzés frissítve + No comment provided by engineer. + + + Record updated at: %@ + A bejegyzés frissítve: %@ + copied message info + + + Reduced battery usage + Csökkentett akkumulátorhasználat + No comment provided by engineer. + + + Reject + Elutasítás + reject incoming call via notification + + + Reject (sender NOT notified) + Elutasítás (a feladó NEM kap értesítést) + No comment provided by engineer. + + + Reject contact request + Kapcsolatfelvételi kérelem elutasítása + No comment provided by engineer. + + + Relay server is only used if necessary. Another party can observe your IP address. + Az átjátszó kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címét. + No comment provided by engineer. + + + Relay server protects your IP address, but it can observe the duration of the call. + Az átjátszó kiszolgáló megvédi IP-címét, de megfigyelheti a hívás időtartamát. + No comment provided by engineer. + + + Remove + Eltávolítás + No comment provided by engineer. + + + Remove member + Tag eltávolítása + No comment provided by engineer. + + + Remove member? + Tag eltávolítása? + No comment provided by engineer. + + + Remove passphrase from keychain? + Jelmondat eltávolítása a kulcstárolóból? + No comment provided by engineer. + + + Renegotiate + Újraegyzetetés + No comment provided by engineer. + + + Renegotiate encryption + Titkosítás újraegyeztetése + No comment provided by engineer. + + + Renegotiate encryption? + Titkosítás újraegyeztetése? + No comment provided by engineer. + + + Repeat connection request? + Kapcsolódási kérés megismétlése? + No comment provided by engineer. + + + Repeat download + No comment provided by engineer. + + + Repeat import + No comment provided by engineer. + + + Repeat join request? + Csatlakozási kérés megismétlése? + No comment provided by engineer. + + + Repeat upload + No comment provided by engineer. + + + Reply + Válasz + chat item action + + + Required + Megkövetelt + No comment provided by engineer. + + + Reset + Alaphelyzetbe állítás + No comment provided by engineer. + + + Reset colors + Színek alaphelyzetbe állítása + No comment provided by engineer. + + + Reset to defaults + Alaphelyzetbe állítás + No comment provided by engineer. + + + Restart the app to create a new chat profile + Új csevegési profil létrehozásához indítsa újra az alkalmazást + No comment provided by engineer. + + + Restart the app to use imported chat database + Az importált csevegési adatbázis használatához indítsa újra az alkalmazást + No comment provided by engineer. + + + Restore + Visszaállítás + No comment provided by engineer. + + + Restore database backup + Adatbázismentés visszaállítása + No comment provided by engineer. + + + Restore database backup? + Adatbázismentés visszaállítása? + No comment provided by engineer. + + + Restore database error + Hiba az adatbázis visszaállításakor + No comment provided by engineer. + + + Retry + Újrapróbálkozás + No comment provided by engineer. + + + Reveal + Felfedés + chat item action + + + Revert + Visszaállít + No comment provided by engineer. + + + Revoke + Visszavonás + No comment provided by engineer. + + + Revoke file + Fájl visszavonása + cancel file action + + + Revoke file? + Fájl visszavonása? + No comment provided by engineer. + + + Role + Szerepkör + No comment provided by engineer. + + + Run chat + Csevegési szolgáltatás indítása + No comment provided by engineer. + + + SMP servers + Üzenetküldő (SMP) kiszolgálók + No comment provided by engineer. + + + Safer groups + No comment provided by engineer. + + + Save + Mentés + chat item action + + + Save (and notify contacts) + Mentés (és az ismerősök értesítése) + No comment provided by engineer. + + + Save and notify contact + Mentés és ismerős értesítése + No comment provided by engineer. + + + Save and notify group members + Mentés és a csoporttagok értesítése + No comment provided by engineer. + + + Save and update group profile + Mentés és a csoport profil frissítése + No comment provided by engineer. + + + Save archive + Archívum mentése + No comment provided by engineer. + + + Save auto-accept settings + Automatikus elfogadási beállítások mentése + No comment provided by engineer. + + + Save group profile + Csoport profil elmentése + No comment provided by engineer. + + + Save passphrase and open chat + Jelmondat elmentése és csevegés megnyitása + No comment provided by engineer. + + + Save passphrase in Keychain + Jelmondat mentése a kulcstárban + No comment provided by engineer. + + + 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 + No comment provided by engineer. + + + Save servers + Kiszolgálók mentése + No comment provided by engineer. + + + Save servers? + Kiszolgálók mentése? + No comment provided by engineer. + + + Save settings? + Beállítások mentése? + No comment provided by engineer. + + + Save welcome message? + Üdvözlőszöveg mentése? + No comment provided by engineer. + + + Saved WebRTC ICE servers will be removed + A mentett WebRTC ICE kiszolgálók eltávolításra kerülnek + No comment provided by engineer. + + + Saved message + Mentett üzenet + message info title + + + Scan QR code + QR-kód beolvasása + No comment provided by engineer. + + + Scan QR code from desktop + QR-kód beolvasása számítógépről + No comment provided by engineer. + + + Scan code + Kód beolvasása + No comment provided by engineer. + + + Scan security code from your contact's app. + Biztonsági kód beolvasása ismerős általi alkalmazásból. + No comment provided by engineer. + + + Scan server QR code + A kiszolgáló QR-kódjának beolvasása + No comment provided by engineer. + + + Search + Keresés + No comment provided by engineer. + + + Search bar accepts invitation links. + A keresősáv fogadja a meghívó hivatkozásokat. + No comment provided by engineer. + + + Search or paste SimpleX link + Keresés, vagy SimpleX hivatkozás beillesztése + No comment provided by engineer. + + + Secure queue + Biztonságos várólista + server test step + + + Security assessment + Biztonsági kiértékelés + No comment provided by engineer. + + + Security code + Biztonsági kód + No comment provided by engineer. + + + Select + Választás + No comment provided by engineer. + + + Self-destruct + Önmegsemmisítés + No comment provided by engineer. + + + Self-destruct passcode + Önmegsemmisítési jelkód + No comment provided by engineer. + + + Self-destruct passcode changed! + Az önmegsemmisítési jelkód megváltozott! + No comment provided by engineer. + + + Self-destruct passcode enabled! + Az önmegsemmisítési jelkód engedélyezve! + No comment provided by engineer. + + + Send + Küldés + No comment provided by engineer. + + + Send a live message - it will update for the recipient(s) as you type it + Élő üzenet küldése - a címzett(ek) számára frissül, ahogy beírja + No comment provided by engineer. + + + Send delivery receipts to + A kézbesítési jelentéseket a következő címre kell küldeni + No comment provided by engineer. + + + Send direct message + Közvetlen üzenet küldése + No comment provided by engineer. + + + Send direct message to connect + A kapcsolódáshoz közvetlen üzenet küldése + No comment provided by engineer. + + + Send disappearing message + Eltűnő üzenet küldése + No comment provided by engineer. + + + Send link previews + Hivatkozás előnézetek küldése + No comment provided by engineer. + + + Send live message + Élő üzenet küldése + No comment provided by engineer. + + + Send notifications + Értesítések küldése + No comment provided by engineer. + + + Send notifications: + Értesítések küldése: + No comment provided by engineer. + + + Send questions and ideas + Ötletek és kérdések beküldése + No comment provided by engineer. + + + Send receipts + Üzenet kézbesítési jelentések + No comment provided by engineer. + + + Send them from gallery or custom keyboards. + Küldje el őket galériából vagy egyedi billentyűzetekről. + No comment provided by engineer. + + + Send up to 100 last messages to new members. + Utolsó 100 üzenet küldése új tagoknak. + No comment provided by engineer. + + + Sender cancelled file transfer. + A küldő megszakította a fájl átvitelt. + No comment provided by engineer. + + + Sender may have deleted the connection request. + A küldő törölhette a kapcsolódási kérelmet. + No comment provided by engineer. + + + Sending delivery receipts will be enabled for all contacts in all visible chat profiles. + A kézbesítési jelentések küldése engedélyezésre kerül az összes látható csevegési profilban lévő minden ismerős számára. + No comment provided by engineer. + + + Sending delivery receipts will be enabled for all contacts. + A kézbesítési jelentés küldése minden ismerős számára engedélyezésre kerül. + No comment provided by engineer. + + + Sending file will be stopped. + A fájl küldése leállt. + No comment provided by engineer. + + + Sending receipts is disabled for %lld contacts + A kézbesítési jelentések küldése le van tiltva %lld ismerősnél + No comment provided by engineer. + + + Sending receipts is disabled for %lld groups + A kézbesítési jelentések küldése le van tiltva %lld csoportban + No comment provided by engineer. + + + Sending receipts is enabled for %lld contacts + A kézbesítési jelentések küldése engedélyezve van %lld ismerős számára + No comment provided by engineer. + + + Sending receipts is enabled for %lld groups + A kézbesítési jelentések küldése engedélyezve van %lld csoportban + No comment provided by engineer. + + + Sending via + Küldés ezen keresztül + No comment provided by engineer. + + + Sent at + Elküldve ekkor + No comment provided by engineer. + + + Sent at: %@ + Elküldve ekkor: %@ + copied message info + + + Sent file event + Elküldött fájl esemény + notification + + + Sent message + Elküldött üzenet + message info title + + + Sent messages will be deleted after set time. + Az elküldött üzenetek törlésre kerülnek a beállított idő után. + No comment provided by engineer. + + + Server requires authorization to create queues, check password + A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát + server test error + + + Server requires authorization to upload, check password + A kiszolgálónak engedélyre van szüksége a várólisták feltöltéséhez, ellenőrizze jelszavát + server test error + + + Server test failed! + A kiszolgáló tesztje sikertelen! + No comment provided by engineer. + + + Servers + Kiszolgálók + No comment provided by engineer. + + + Session code + Munkamenet kód + No comment provided by engineer. + + + Set 1 day + Beállítva 1 nap + No comment provided by engineer. + + + Set contact name… + Ismerős nevének beállítása… + No comment provided by engineer. + + + Set group preferences + Csoportbeállítások megadása + No comment provided by engineer. + + + Set it instead of system authentication. + Rendszerhitelesítés helyetti beállítás. + No comment provided by engineer. + + + Set passcode + Jelkód beállítása + No comment provided by engineer. + + + Set passphrase + No comment provided by engineer. + + + Set passphrase to export + Jelmondat beállítása az exportáláshoz + No comment provided by engineer. + + + Set the message shown to new members! + Megjelenő üzenetet beállítása új tagok részére! + No comment provided by engineer. + + + Set timeouts for proxy/VPN + Időtúllépések beállítása a proxy/VPN számára + No comment provided by engineer. + + + Settings + Beállítások + No comment provided by engineer. + + + Share + Megosztás + chat item action + + + Share 1-time link + Egyszer használatos hivatkozás megosztása + No comment provided by engineer. + + + Share address + Azonosító megosztása + No comment provided by engineer. + + + Share address with contacts? + Megosztja az azonosítót az ismerősökkel? + No comment provided by engineer. + + + Share link + Hivatkozás megosztása + No comment provided by engineer. + + + Share this 1-time invite link + Egyszer használatos meghívó hivatkozás megosztása + No comment provided by engineer. + + + Share with contacts + Megosztás ismerősökkel + No comment provided by engineer. + + + Show QR code + No comment provided by engineer. + + + Show calls in phone history + Hívások megjelenítése a híváslistában + No comment provided by engineer. + + + Show developer options + Fejlesztői beállítások mutatása + No comment provided by engineer. + + + Show last messages + Utolsó üzenetek megjelenítése + No comment provided by engineer. + + + Show preview + Előnézet megjelenítése + No comment provided by engineer. + + + Show: + Mutat: + No comment provided by engineer. + + + SimpleX Address + SimpleX azonosító + No comment provided by engineer. + + + SimpleX Chat security was audited by Trail of Bits. + A SimpleX Chat biztonsága a Trail of Bits által lett auditálva. + No comment provided by engineer. + + + SimpleX Lock + SimpleX zárolás + No comment provided by engineer. + + + SimpleX Lock mode + SimpleX zárolási mód + No comment provided by engineer. + + + SimpleX Lock not enabled! + SimpleX zárolás nincs engedélyezve! + No comment provided by engineer. + + + SimpleX Lock turned on + SimpleX zárolás bekapcsolva + No comment provided by engineer. + + + SimpleX address + SimpleX azonosító + No comment provided by engineer. + + + SimpleX contact address + SimpleX ismerős azonosítója + simplex link type + + + SimpleX encrypted message or connection event + SimpleX titkosított üzenet vagy kapcsolati esemény + notification + + + SimpleX group link + SimpleX csoport hivatkozás + simplex link type + + + SimpleX links + SimpleX hivatkozások + No comment provided by engineer. + + + SimpleX one-time invitation + SimpleX egyszer használatos meghívó + simplex link type + + + Simplified incognito mode + Egyszerűsített inkognító mód + No comment provided by engineer. + + + Skip + Kihagyás + No comment provided by engineer. + + + Skipped messages + Kihagyott üzenetek + No comment provided by engineer. + + + Small groups (max 20) + Kis csoportok (max. 20 tag) + No comment provided by engineer. + + + Some non-fatal errors occurred during import - you may see Chat console for more details. + Néhány nem végzetes hiba történt az importálás során – további részletekért a csevegési konzolban olvashat. + No comment provided by engineer. + + + Somebody + Valaki + notification title + + + Start chat + Csevegés indítása + No comment provided by engineer. + + + Start chat? + Csevegés indítása? + No comment provided by engineer. + + + Start migration + Migráció indítása + No comment provided by engineer. + + + Stop + Megállítás + No comment provided by engineer. + + + Stop SimpleX + A SimpleX megállítása + authentication reason + + + Stop chat + No comment provided by engineer. + + + Stop chat to enable database actions + Csevegés leállítása az adatbázis-műveletek engedélyezéséhez + No comment provided by engineer. + + + Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped. + A csevegés leállítása a csevegőadatbázis exportálásához, importálásához vagy törléséhez. A csevegés leállítása alatt nem tud üzeneteket fogadni és küldeni. + No comment provided by engineer. + + + Stop chat? + Csevegési szolgáltatás megállítása? + No comment provided by engineer. + + + Stop file + Fájl megállítása + cancel file action + + + Stop receiving file? + Fájl fogadás megszakítása? + No comment provided by engineer. + + + Stop sending file? + Fájl küldés megszakítása? + No comment provided by engineer. + + + Stop sharing + Megosztás leállítása + No comment provided by engineer. + + + Stop sharing address? + Címmegosztás megállítása? + No comment provided by engineer. + + + Stopping chat + No comment provided by engineer. + + + Submit + Elküldés + No comment provided by engineer. + + + Support SimpleX Chat + Támogassa a SimpleX Chatet + No comment provided by engineer. + + + System + Rendszer + No comment provided by engineer. + + + System authentication + Rendszerhitelesítés + No comment provided by engineer. + + + TCP connection timeout + TCP kapcsolat időtúllépés + No comment provided by engineer. + + + TCP_KEEPCNT + TCP_KEEPCNT + No comment provided by engineer. + + + TCP_KEEPIDLE + TCP_KEEPIDLE + No comment provided by engineer. + + + TCP_KEEPINTVL + TCP_KEEPINTVL + No comment provided by engineer. + + + Take picture + Fotó készítése + No comment provided by engineer. + + + Tap button + Koppintson a gombra + No comment provided by engineer. + + + Tap to Connect + Koppintson a csatlakozáshoz + No comment provided by engineer. + + + Tap to activate profile. + A profil aktiválásához koppintson az ikonra. + No comment provided by engineer. + + + Tap to join + Koppintson a csatlakozáshoz + No comment provided by engineer. + + + Tap to join incognito + Koppintson az inkognitómódhoz való csatlakozáshoz + No comment provided by engineer. + + + Tap to paste link + Koppintson a hivatkozás beillesztéséhez + No comment provided by engineer. + + + Tap to scan + Koppintson a beolvasáshoz + No comment provided by engineer. + + + Tap to start a new chat + Koppintson az új csevegés indításához + No comment provided by engineer. + + + Test failed at step %@. + A teszt sikertelen volt a(z) %@ lépésnél. + server test failure + + + Test server + Kiszolgáló tesztelése + No comment provided by engineer. + + + Test servers + Kiszolgálók tesztelése + No comment provided by engineer. + + + Tests failed! + Sikertelen tesztek! + No comment provided by engineer. + + + Thank you for installing SimpleX Chat! + Köszönjük, hogy telepítette a SimpleX Chatet! + No comment provided by engineer. + + + Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! + Köszönet a felhasználóknak – [hozzájárulás a Weblate-en keresztül](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)! + No comment provided by engineer. + + + Thanks to the users – contribute via Weblate! + Köszönet a felhasználóknak - hozzájárulás a Weblaten! + No comment provided by engineer. + + + The 1st platform without any user identifiers – private by design. + Az első csevegési rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre. + No comment provided by engineer. + + + The ID of the next message is incorrect (less or equal to the previous). +It can happen because of some bug or when the connection is compromised. + A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel). +Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. + 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 kapcsolatfelvételi kéréseket kap – beállítások megnyitása az engedélyezéshez. + No comment provided by engineer. + + + The attempt to change database passphrase was not completed. + Az adatbázis jelmondatának megváltoztatására tett kísérlet nem fejeződött be. + No comment provided by engineer. + + + The code you scanned is not a SimpleX link QR code. + A beolvasott kód nem egy SimpleX hivatkozás QR-kód. + No comment provided by engineer. + + + The connection you accepted will be cancelled! + Az ön által elfogadott kapcsolat megszakad! + No comment provided by engineer. + + + The contact you shared this link with will NOT be able to connect! + Ismerőse NEM fog tudni csatlakozni, akivel megosztotta ezt a hivatkozást! + No comment provided by engineer. + + + The created archive is available via app Settings / Database / Old database archive. + A létrehozott archívum a Beállítások / Adatbázis / Régi adatbázis-archívum menüpontban érhető el. + No comment provided by engineer. + + + The encryption is working and the new encryption agreement is not required. It may result in connection errors! + A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet! + No comment provided by engineer. + + + The hash of the previous message is different. + Az előző üzenet hash-e más. + No comment provided by engineer. + + + The message will be deleted for all members. + Az üzenet minden tag számára törlésre kerül. + No comment provided by engineer. + + + The message will be marked as moderated for all members. + Az üzenet minden tag számára moderáltként lesz megjelölve. + No comment provided by engineer. + + + The next generation of private messaging + A privát üzenetküldés következő generációja + No comment provided by engineer. + + + The old database was not removed during the migration, it can be deleted. + A régi adatbázis nem került eltávolításra a migráció során, így törölhető. + No comment provided by engineer. + + + The profile is only shared with your contacts. + Profilja csak az ismerősök számára kerül megosztásra. + No comment provided by engineer. + + + The second tick we missed! ✅ + A második jelölés, amit kihagytunk! ✅ + No comment provided by engineer. + + + The sender will NOT be notified + A feladó NEM fog értesítést kapni + No comment provided by engineer. + + + The servers for new connections of your current chat profile **%@**. + Jelenlegi profil új ismerőseinek kiszolgálói **%@**. + No comment provided by engineer. + + + The text you pasted is not a SimpleX link. + A beillesztett szöveg nem egy SimpleX hivatkozás. + No comment provided by engineer. + + + Theme + Téma + No comment provided by engineer. + + + These settings are for your current profile **%@**. + Ezek a beállítások a jelenlegi **%@** profiljára vonatkoznak. + No comment provided by engineer. + + + They can be overridden in contact and group settings. + Ezek felülbírálhatóak az ismerős- és csoportbeállításokban. + No comment provided by engineer. + + + This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain. + Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalommal együtt törlésre kerülnek. Az alacsony felbontású fotók viszont megmaradnak. + No comment provided by engineer. + + + This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes. + 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. + No comment provided by engineer. + + + This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost. + Ez a művelet nem vonható vissza - profilok, ismerősök, üzenetek és fájlok visszafordíthatatlanul törlésre kerülnek. + No comment provided by engineer. + + + This chat is protected by end-to-end encryption. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + E2EE info chat item + + + This device name + Ennek az eszköznek a neve + No comment provided by engineer. + + + This display name is invalid. Please choose another name. + Ez a megjelenített felhasználónév érvénytelen. Válasszon egy másik nevet. + No comment provided by engineer. + + + This group has over %lld members, delivery receipts are not sent. + Ennek a csoportnak több mint %lld tagja van, a kézbesítési jelentések nem kerülnek elküldésre. + No comment provided by engineer. + + + This group no longer exists. + Ez a csoport már nem létezik. + No comment provided by engineer. + + + This is your own SimpleX address! + Ez a SimpleX azonosítója! + No comment provided by engineer. + + + This is your own one-time link! + Ez az egyszer használatos hivatkozása! + No comment provided by engineer. + + + This setting applies to messages in your current chat profile **%@**. + Ez a beállítás a jelenlegi **%@** profiljában lévő üzenetekre érvényes. + No comment provided by engineer. + + + To ask any questions and to receive updates: + Bármilyen kérdés feltevéséhez és a frissítésekért: + No comment provided by engineer. + + + To connect, your contact can scan QR code or use the link in the app. + A csatlakozáshoz az ismerős beolvashatja a QR-kódot, vagy használhatja az alkalmazásban található hivatkozást. + No comment provided by engineer. + + + To hide unwanted messages. + Kéretlen üzenetek elrejtése. + No comment provided by engineer. + + + 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 üzenetsorokhoz rendel azonosítókat, minden egyes ismerőshöz 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. + No comment provided by engineer. + + + To protect your information, turn on SimpleX Lock. +You will be prompted to complete authentication before this feature is enabled. + Az adatavédelem érdekében kapcsolja be a SimpleX zárolás funkciót. +A funkció engedélyezése előtt a rendszer felszólítja a hitelesítés befejezésére. + No comment provided by engineer. + + + To record voice message please grant permission to use Microphone. + Hangüzenet rögzítéséhez adjon engedélyt a mikrofon használathoz. + No comment provided by engineer. + + + To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page. + Rejtett profilja feltárásához írja be a teljes jelszót a keresőmezőbe a **Csevegési profiljai** oldalon. + No comment provided by engineer. + + + To support instant push notifications the chat database has to be migrated. + Az azonnali push értesítések támogatásához a csevegési adatbázis migrálása szükséges. + No comment provided by engineer. + + + To verify end-to-end encryption with your contact compare (or scan) the code on your devices. + A végpontok közötti titkosítás ellenőrzéséhez ismerősével hasonlítsa össze (vagy szkennelje be) az eszközén lévő kódot. + No comment provided by engineer. + + + Toggle incognito when connecting. + Inkognító mód csatlakozáskor. + No comment provided by engineer. + + + Transport isolation + Kapcsolat izolációs mód + No comment provided by engineer. + + + Trying to connect to the server used to receive messages from this contact (error: %@). + Csatlakozási kísérlet a kapcsolat üzeneteinek fogadására használt kiszolgálóhoz (hiba: %@). + No comment provided by engineer. + + + Trying to connect to the server used to receive messages from this contact. + Csatlakozási kísérlet a kapcsolat üzeneteinek fogadására használt kiszolgálóhoz ettől az ismerőstől. + No comment provided by engineer. + + + Turkish interface + Török kezelőfelület + No comment provided by engineer. + + + Turn off + Kikapcsolás + No comment provided by engineer. + + + Turn on + Bekapcsolás + No comment provided by engineer. + + + Unable to record voice message + Hangüzenet rögzítése nem lehetséges + No comment provided by engineer. + + + Unblock + Feloldás + No comment provided by engineer. + + + Unblock for all + Letiltás feloldása mindenki számára + No comment provided by engineer. + + + Unblock member + Tag feloldása + No comment provided by engineer. + + + Unblock member for all? + Mindenki számára feloldja a tag letiltását? + No comment provided by engineer. + + + Unblock member? + Tag feloldása? + No comment provided by engineer. + + + Unexpected error: %@ + Váratlan hiba: %@ + item status description + + + Unexpected migration state + Váratlan migrációs állapot + No comment provided by engineer. + + + Unfav. + Nem kedvelt. + No comment provided by engineer. + + + Unhide + Felfedés + No comment provided by engineer. + + + Unhide chat profile + Csevegési profil felfedése + No comment provided by engineer. + + + Unhide profile + Profil felfedése + No comment provided by engineer. + + + Unit + Egység + No comment provided by engineer. + + + Unknown caller + Ismeretlen hívó + callkit banner + + + Unknown database error: %@ + Ismeretlen adatbázishiba: %@ + No comment provided by engineer. + + + Unknown error + Ismeretlen hiba + No comment provided by engineer. + + + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. + Hacsak nem az iOS hívási felületét használja, engedélyezze a Ne zavarjanak módot a megszakítások elkerülése érdekében. + No comment provided by engineer. + + + Unless your contact deleted the connection or this link was already used, it might be a bug - please report it. +To connect, please ask your contact to create another connection link and check that you have a stable network connection. + Hacsak az ismerős nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, hiba lehet – kérjük, jelentse. +A csatlakozáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsolati hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e. + No comment provided by engineer. + + + Unlink + Szétkapcsolás + No comment provided by engineer. + + + Unlink desktop? + Számítógép szétkapcsolása? + No comment provided by engineer. + + + Unlock + Feloldás + No comment provided by engineer. + + + Unlock app + Alkalmazás feloldása + authentication reason + + + Unmute + Némítás feloldása + No comment provided by engineer. + + + Unread + Olvasatlan + No comment provided by engineer. + + + Up to 100 last messages are sent to new members. + Legfeljebb az utolsó 100 üzenet kerül elküldésre az új tagoknak. + No comment provided by engineer. + + + Update + Frissítés + No comment provided by engineer. + + + Update .onion hosts setting? + Tor .onion host beállítások frissítése? + No comment provided by engineer. + + + Update database passphrase + Adatbázis jelmondat megváltoztatása + No comment provided by engineer. + + + Update network settings? + Hálózati beállítások megváltoztatása? + No comment provided by engineer. + + + Update transport isolation mode? + Kapcsolat izolációs mód frissítése? + No comment provided by engineer. + + + Updating settings will re-connect the client to all servers. + A beállítások frissítése a szerverekhez újra kapcsolódással jár. + No comment provided by engineer. + + + Updating this setting will re-connect the client to all servers. + A beállítás frissítésével a kliens újracsatlakozik az összes kiszolgálóhoz. + No comment provided by engineer. + + + Upgrade and open chat + A csevegés frissítése és megnyitása + No comment provided by engineer. + + + Upload failed + No comment provided by engineer. + + + Upload file + Fájl feltöltése + server test step + + + Uploading archive + No comment provided by engineer. + + + Use .onion hosts + Tor .onion hostok használata + No comment provided by engineer. + + + Use SimpleX Chat servers? + SimpleX Chat kiszolgálók használata? + No comment provided by engineer. + + + Use chat + Csevegés használata + No comment provided by engineer. + + + Use current profile + Jelenlegi profil használata + No comment provided by engineer. + + + Use for new connections + Alkalmazás új kapcsolatokhoz + No comment provided by engineer. + + + Use from desktop + Használat számítógépről + No comment provided by engineer. + + + Use iOS call interface + Az iOS hívófelület használata + No comment provided by engineer. + + + Use new incognito profile + Az új inkognító profil használata + No comment provided by engineer. + + + Use only local notifications? + Csak helyi értesítések használata? + No comment provided by engineer. + + + Use server + Kiszolgáló használata + No comment provided by engineer. + + + Use the app while in the call. + No comment provided by engineer. + + + User profile + Felhasználói profil + No comment provided by engineer. + + + Using .onion hosts requires compatible VPN provider. + A .onion hosztok használatához kompatibilis VPN szolgáltatóra van szükség. + No comment provided by engineer. + + + Using SimpleX Chat servers. + SimpleX Chat kiszolgálók használatban. + No comment provided by engineer. + + + Verify code with desktop + Kód ellenőrzése a számítógépen + No comment provided by engineer. + + + Verify connection + Kapcsolat ellenőrzése + No comment provided by engineer. + + + Verify connection security + Kapcsolat biztonságának ellenőrzése + No comment provided by engineer. + + + Verify connections + Kapcsolatok ellenőrzése + No comment provided by engineer. + + + Verify database passphrase + No comment provided by engineer. + + + Verify passphrase + No comment provided by engineer. + + + Verify security code + Biztonsági kód ellenőrzése + No comment provided by engineer. + + + Via browser + Böngészőn keresztül + No comment provided by engineer. + + + Via secure quantum resistant protocol. + Biztonságos kvantum ellenálló protokoll által. + No comment provided by engineer. + + + Video call + Videóhívás + No comment provided by engineer. + + + Video will be received when your contact completes uploading it. + A videó akkor érkezik meg, amikor az ismerőse befejezte annak feltöltését. + No comment provided by engineer. + + + Video will be received when your contact is online, please wait or check later! + A videó akkor érkezik meg, amikor az ismerős elérhető, várjon, vagy ellenőrizze később! + No comment provided by engineer. + + + Videos and files up to 1gb + Videók és fájlok 1Gb méretig + No comment provided by engineer. + + + View security code + Biztonsági kód megtekintése + No comment provided by engineer. + + + Visible history + Látható előzmények + chat feature + + + Voice messages + Hangüzenetek + chat feature + + + Voice messages are prohibited in this chat. + A hangüzenetek le vannak tiltva ebben a csevegésben. + No comment provided by engineer. + + + Voice messages are prohibited in this group. + A hangüzenetek küldése le van tiltva ebben a csoportban. + No comment provided by engineer. + + + Voice messages prohibited! + A hangüzenetek le vannak tilva! + No comment provided by engineer. + + + Voice message… + Hangüzenet… + No comment provided by engineer. + + + Waiting for desktop... + Várakozás az asztali kliensre... + No comment provided by engineer. + + + Waiting for file + Fájlra várakozás + No comment provided by engineer. + + + Waiting for image + Képre várakozás + No comment provided by engineer. + + + Waiting for video + Videóra várakozás + No comment provided by engineer. + + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + No comment provided by engineer. + + + Warning: you may lose some data! + Figyelmeztetés: néhány adat elveszhet! + No comment provided by engineer. + + + WebRTC ICE servers + WebRTC ICE kiszolgálók + No comment provided by engineer. + + + Welcome %@! + Üdvözöllek %@! + No comment provided by engineer. + + + Welcome message + Üdvözlő üzenet + No comment provided by engineer. + + + Welcome message is too long + No comment provided by engineer. + + + What's new + Milyen újdonságok vannak + No comment provided by engineer. + + + When available + Amikor elérhető + No comment provided by engineer. + + + When people request to connect, you can accept or reject it. + Csatlakozási kérelmek esetében, elfogadhatja vagy elutasíthatja azokat. + No comment provided by engineer. + + + When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. + Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. + No comment provided by engineer. + + + With encrypted files and media. + Titkosított fájlokkal és médiatartalommal. + No comment provided by engineer. + + + With optional welcome message. + Opcionális üdvözlő üzenettel. + No comment provided by engineer. + + + With reduced battery usage. + Csökkentett akkumulátorhasználattal. + No comment provided by engineer. + + + Wrong database passphrase + Téves adatbázis jelmondat + No comment provided by engineer. + + + Wrong passphrase! + Téves jelmondat! + No comment provided by engineer. + + + XFTP servers + XFTP kiszolgálók + No comment provided by engineer. + + + You + Ön + No comment provided by engineer. + + + You **must not** use the same database on two devices. + No comment provided by engineer. + + + You accepted connection + Kapcsolódás elfogadva + No comment provided by engineer. + + + You allow + Engedélyezte + No comment provided by engineer. + + + You already have a chat profile with the same display name. Please choose another name. + Már van egy csevegési profil ugyanezzel a megjelenített névvel. Válasszon egy másik nevet. + No comment provided by engineer. + + + You are already connected to %@. + Már csatlakozva van ehhez: %@. + No comment provided by engineer. + + + You are already connecting to %@. + Már folyamatban van a csatlakozás ehhez: %@. + No comment provided by engineer. + + + You are already connecting via this one-time link! + Már csatlakozik ezen az egyszer használatos hivatkozáson keresztül! + No comment provided by engineer. + + + You are already in group %@. + Már a %@ csoportban van. + No comment provided by engineer. + + + You are already joining the group %@. + Már folyamatban van a csatlakozás a csoporthoz %@. + No comment provided by engineer. + + + You are already joining the group via this link! + Már csatlakozott a csoporthoz ezen a hivatkozáson keresztül! + No comment provided by engineer. + + + You are already joining the group via this link. + Ezen a hivatkozáson keresztül már csatlakozik a csoporthoz. + No comment provided by engineer. + + + You are already joining the group! +Repeat join request? + Csatlakozás folyamatban! +Csatlakozási kérés megismétlése? + No comment provided by engineer. + + + You are connected to the server used to receive messages from this contact. + Kiszolgálóhoz történő csatlakozás, mely az adott ismerőstől érkező üzenetek fogadására szolgál. + No comment provided by engineer. + + + You are invited to group + Meghívást kapott a csoportba + No comment provided by engineer. + + + You can accept calls from lock screen, without device and app authentication. + Hívásokat fogadhat a lezárási képernyőről, eszköz- és alkalmazáshitelesítés nélkül. + No comment provided by engineer. + + + You can create it later + Létrehozás később + No comment provided by engineer. + + + You can enable later via Settings + Később engedélyezheti a Beállításokban + 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üpontban. + No comment provided by engineer. + + + You can give another try. + No comment provided by engineer. + + + You can hide or mute a user profile - swipe it to the right. + Elrejthet vagy némíthat egy felhasználói profilt – csúsztasson jobbra. + No comment provided by engineer. + + + You can make it visible to your SimpleX contacts via Settings. + Láthatóvá teheti SimpleX ismerősök számára a Beállításokban. + No comment provided by engineer. + + + You can now send messages to %@ + Mostantól küldhet üzeneteket %@ számára + notification body + + + You can set lock screen notification preview via settings. + A beállításokon keresztül beállíthatja a lezárási képernyő értesítési előnézetét. + No comment provided by engineer. + + + You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it. + Megoszthat egy hivatkozást vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Ha a csoport később törlésre kerül, akkor nem fogja elveszíteni annak tagjait. + No comment provided by engineer. + + + You can share this address with your contacts to let them connect with **%@**. + Megoszthatja ezt a hivatkozást ismerőseivel, hogy kapcsolatba léphessenek önnel a **%@** nevű profilján keresztül. + No comment provided by engineer. + + + You can share your address as a link or QR code - anybody can connect to you. + Megoszthatja azonosítóját hivatkozásként vagy QR-kódként – így bárki csatlakozhat önhöz. + No comment provided by engineer. + + + You can start chat via app Settings / Database or by restarting the app + A csevegést az alkalmazás Beállítások / Adatbázis menü segítségével vagy az alkalmazás újraindításával indíthatja el + No comment provided by engineer. + + + You can turn on SimpleX Lock via Settings. + A SimpleX zárolás a Beállításokon keresztül kapcsolható be. + No comment provided by engineer. + + + You can use markdown to format messages: + Üzenetek formázása a szövegbe szúrt speciális karakterekkel: + No comment provided by engineer. + + + You can view invitation link again in connection details. + A meghívó hivatkozást újra megtekintheti a kapcsolat részleteinél. + No comment provided by engineer. + + + You can't send messages! + Nem lehet üzeneteket küldeni! + No comment provided by engineer. + + + You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them. + Ön szabályozhatja, hogy mely kiszogál(ók)ón keresztül **kapja** az üzeneteket, az ismerősöket - az üzenetküldéshez használt szervereken. + No comment provided by engineer. + + + You could not be verified; please try again. + Nem lehetett ellenőrizni; próbálja meg újra. + No comment provided by engineer. + + + You have already requested connection via this address! + Már kért egy csatlakozást ezen az azonosítón keresztül! + No comment provided by engineer. + + + You have already requested connection! +Repeat connection request? + Már kérelmezte a csatlakozást! +Kapcsolódási kérés megismétlése? + No comment provided by engineer. + + + You have no chats + Nincsenek csevegési üzenetek + No comment provided by engineer. + + + You have to enter passphrase every time the app starts - it is not stored on the device. + A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul - nem az eszközön kerül tárolásra. + No comment provided by engineer. + + + You invited a contact + Meghívott egy ismerőst + No comment provided by engineer. + + + You joined this group + Csatlakozott ehhez a csoporthoz + No comment provided by engineer. + + + You joined this group. Connecting to inviting group member. + Csatlakozott ehhez a csoporthoz. Kapcsolódás a meghívó csoporttaghoz. + No comment provided by engineer. + + + You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts. + A csevegési adatbázis legfrissebb verzióját CSAK egy eszközön kell használnia, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogja megkapni valamennyi ismerőstől. + No comment provided by engineer. + + + You need to allow your contact to send voice messages to be able to send them. + Hangüzeneteket küldéséhez engedélyeznie kell azok küldését az ismerősök számára. + No comment provided by engineer. + + + You rejected group invitation + Csoport meghívó elutasítva + No comment provided by engineer. + + + You sent group invitation + Csoport meghívó elküldve + No comment provided by engineer. + + + You will be connected to group when the group host's device is online, please wait or check later! + Akkor tud csatlakozni a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! + No comment provided by engineer. + + + You will be connected when group link host's device is online, please wait or check later! + Akkor lesz csatlakoztatva, amikor a csoportos hivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később! + No comment provided by engineer. + + + You will be connected when your connection request is accepted, please wait or check later! + Akkor lesz csatlakoztatva, ha a csatlakozási kérelme elfogadásra került, várjon, vagy ellenőrizze később! + No comment provided by engineer. + + + You will be connected when your contact's device is online, please wait or check later! + Akkor csatlakozik, amikor az ismerős eszköze online lesz, várjon, vagy ellenőrizze később! + No comment provided by engineer. + + + You will be required to authenticate when you start or resume the app after 30 seconds in background. + Az alkalmazás indításakor, vagy 30 másodpercnyi háttérben töltött idő után az alkalmazáshoz visszatérve hitelesítés szükséges. + No comment provided by engineer. + + + You will connect to all group members. + Csatlakozni fog a csoport összes tagjához. + No comment provided by engineer. + + + You will still receive calls and notifications from muted profiles when they are active. + Továbbra is kap hívásokat és értesítéseket a némított profiloktól, ha azok aktívak. + No comment provided by engineer. + + + You will stop receiving messages from this group. Chat history will be preserved. + Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak. + No comment provided by engineer. + + + You won't lose your contacts if you later delete your address. + Nem veszíti el ismerőseit, ha később törli az azonosítóját. + No comment provided by engineer. + + + You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile + Egy olyan ismerőst próbál meghívni, akivel inkognító profilt osztott meg abban a csoportban, amelyben saját fő profilja van használatban + No comment provided by engineer. + + + You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed + Inkognító profilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében meghívók küldése tiltott + No comment provided by engineer. + + + Your %@ servers + %@ nevű profiljához tartozó kiszolgálók + No comment provided by engineer. + + + Your ICE servers + ICE kiszolgálók + No comment provided by engineer. + + + Your SMP servers + SMP kiszolgálók + No comment provided by engineer. + + + Your SimpleX address + SimpleX azonosítója + No comment provided by engineer. + + + Your XFTP servers + XFTP kiszolgálók + No comment provided by engineer. + + + Your calls + Hívások + No comment provided by engineer. + + + Your chat database + Csevegési adatbázisa + No comment provided by engineer. + + + Your chat database is not encrypted - set passphrase to encrypt it. + Csevegési adatbázisa nincs titkosítva – adjon meg egy jelmondatot a titkosításhoz. + No comment provided by engineer. + + + Your chat profiles + Csevegési profiljai + No comment provided by engineer. + + + Your contact needs to be online for the connection to complete. +You can cancel this connection and remove the contact (and try later with a new link). + Az ismerősnek online kell lennie ahhoz, hogy a kapcsolat létrejöjjön. +Megszakíthatja ezt a kapcsolatfelvételt és törölheti az ismerőst (ezt később ismét megpróbálhatja egy új hivatkozással). + No comment provided by engineer. + + + Your contact sent a file that is larger than currently supported maximum size (%@). + Ismerőse olyan fájlt küldött, amely meghaladja a jelenleg támogatott maximális méretet (%@). + No comment provided by engineer. + + + Your contacts can allow full message deletion. + Ismerősök engedélyezhetik a teljes üzenet törlést. + No comment provided by engineer. + + + Your contacts will remain connected. + Az ismerősök továbbra is csatlakoztatva maradnak. + No comment provided by engineer. + + + Your current chat database will be DELETED and REPLACED with the imported one. + A jelenlegi csevegési adatbázis TÖRLŐDNI FOG, és a HELYÉRE az importált adatbázis kerül. + No comment provided by engineer. + + + Your current profile + Jelenlegi profil + No comment provided by engineer. + + + Your preferences + Beállítások + No comment provided by engineer. + + + Your privacy + Adatvédelem + No comment provided by engineer. + + + Your profile + Profil + No comment provided by engineer. + + + Your profile **%@** will be shared. + **%@** nevű profilja megosztásra kerül. + No comment provided by engineer. + + + Your profile is stored on your device and shared only with your contacts. +SimpleX servers cannot see your profile. + Profilja az eszközön van tárolva, és csak az ismerősökkel kerül megosztásra. +A SimpleX kiszolgálók nem látjhatják profilját. + No comment provided by engineer. + + + Your profile, contacts and delivered messages are stored on your device. + Profilja, ismerősök és az elküldött üzenetek az eszközön kerülnek tárolásra. + No comment provided by engineer. + + + Your random profile + Véletlenszerű profil + No comment provided by engineer. + + + Your server + Saját kiszolgáló + No comment provided by engineer. + + + Your server address + Saját kiszolgáló cím + No comment provided by engineer. + + + Your settings + Beállítások + No comment provided by engineer. + + + [Contribute](https://github.com/simplex-chat/simplex-chat#contribute) + [Hozzájárulás](https://github.com/simplex-chat/simplex-chat#contribute) + No comment provided by engineer. + + + [Send us email](mailto:chat@simplex.chat) + [Küldjön nekünk e-mailt](mailto:chat@simplex.chat) + No comment provided by engineer. + + + [Star on GitHub](https://github.com/simplex-chat/simplex-chat) + [Csillag a GitHubon](https://github.com/simplex-chat/simplex-chat) + No comment provided by engineer. + + + \_italic_ + \_dőlt_ + No comment provided by engineer. + + + \`a + b` + a + b + No comment provided by engineer. + + + above, then choose: + fent, majd válassza ki: + No comment provided by engineer. + + + accepted call + elfogadott hívás + call status + + + admin + admin + member role + + + agreeing encryption for %@… + titkosítás jóváhagyása %@ számára… + chat item text + + + agreeing encryption… + titkosítás elfogadása… + chat item text + + + always + mindig + pref value + + + and %lld other events + és %lld egyéb esemény + No comment provided by engineer. + + + audio call (not e2e encrypted) + hanghívás (nem e2e titkosított) + No comment provided by engineer. + + + author + szerző + member role + + + bad message ID + téves üzenet ID + integrity error chat item + + + bad message hash + téves üzenet hash + integrity error chat item + + + blocked + blokkolva + marked deleted chat item preview text + + + blocked %@ + %@ letiltva + rcv group event chat item + + + blocked by admin + letiltva az admin által + marked deleted chat item preview text + + + bold + félkövér + No comment provided by engineer. + + + call error + hiba a hívásban + call status + + + call in progress + hívás folyamatban + call status + + + calling… + hívás… + call status + + + cancelled %@ + %@ törölve + feature offered item + + + changed address for you + Cím megváltoztatva + chat item text + + + changed role of %1$@ to %2$@ + %1$@ szerepköre megváltozott erre: %2$@ + rcv group event chat item + + + changed your role to %@ + megváltoztatta a szerepkörét erre: %@ + rcv group event chat item + + + changing address for %@… + cím módosítása %@ számára… + chat item text + + + changing address… + azonosító megváltoztatása… + chat item text + + + colored + színes + No comment provided by engineer. + + + complete + befejezett + No comment provided by engineer. + + + connect to SimpleX Chat developers. + Csatlakozás a SimpleX Chat fejlesztőkhöz. + No comment provided by engineer. + + + connected + kapcsolódva + No comment provided by engineer. + + + connected directly + közvetlenül kapcsolódva + rcv group event chat item + + + connecting + kapcsolódás + No comment provided by engineer. + + + connecting (accepted) + kapcsolódás (elfogadva) + No comment provided by engineer. + + + connecting (announced) + kapcsolódás (bejelentve) + No comment provided by engineer. + + + connecting (introduced) + kapcsolódás (bejelentve) + No comment provided by engineer. + + + connecting (introduction invitation) + csatlakozás (bemutatkozás meghívás) + No comment provided by engineer. + + + connecting call… + hívás kapcsolódik… + call status + + + connecting… + kapcsolódás… + chat list item title + + + connection established + Kapcsolat létrehozva + chat list item title (it should not be shown + + + connection:%@ + kapcsolat: %@ + connection information + + + contact %1$@ changed to %2$@ + %1$@ ismerősének neve megváltozott erre: %2$@ + profile update event chat item + + + contact has e2e encryption + az ismerősnél az e2e titkosítás elérhető + No comment provided by engineer. + + + contact has no e2e encryption + az ismerősnél az e2e titkosítás nem elérhető + No comment provided by engineer. + + + creator + szerző + No comment provided by engineer. + + + custom + egyedi + dropdown time picker choice + + + database version is newer than the app, but no down migration for: %@ + az adatbázis verziója újabb, mint az alkalmazásé, de nincs visszafelé migráció: %@ + No comment provided by engineer. + + + days + nap + time unit + + + default (%@) + alapértelmezett (%@) + pref value + + + default (no) + alapértelmezett (nem) + No comment provided by engineer. + + + default (yes) + alapértelmezett (igen) + No comment provided by engineer. + + + deleted + törölve + deleted chat item + + + deleted contact + törölt ismerős + rcv direct event chat item + + + deleted group + törölt csoport + rcv group event chat item + + + different migration in the app/database: %@ / %@ + különböző migrációk az alkalmazásban/adatbázisban: %@ / %@ + No comment provided by engineer. + + + direct + közvetlen + connection level description + + + disabled + letiltva + No comment provided by engineer. + + + duplicate message + duplikálódott üzenet + integrity error chat item + + + e2e encrypted + e2e titkosított + No comment provided by engineer. + + + enabled + engedélyezve + enabled status + + + enabled for contact + engedélyezve ismerős részére + enabled status + + + enabled for you + engedélyezve az ön számára + enabled status + + + encryption agreed + titkosítás egyeztetve + chat item text + + + encryption agreed for %@ + titkosítás elfogadva %@ számára + chat item text + + + encryption ok + titkosítás rendben + chat item text + + + encryption ok for %@ + titkosítás rendben vele: %@ + chat item text + + + encryption re-negotiation allowed + titkosítás újraegyeztetés engedélyezve + chat item text + + + encryption re-negotiation allowed for %@ + titkosítás újraegyeztetés engedélyezve vele: %@ + chat item text + + + encryption re-negotiation required + titkosítás újraegyeztetés szükséges + chat item text + + + encryption re-negotiation required for %@ + titkosítás újraegyeztetés szükséges %@ számára + chat item text + + + ended + befejeződött + No comment provided by engineer. + + + ended call %@ + %@ hívása befejeződött + call status + + + error + hiba + No comment provided by engineer. + + + event happened + esemény történt + No comment provided by engineer. + + + group deleted + a csoport törölve + No comment provided by engineer. + + + group profile updated + csoport profil frissítve + snd group event chat item + + + hours + óra + time unit + + + iOS Keychain is used to securely store passphrase - it allows receiving push notifications. + Az iOS kulcstár a jelmondat biztonságos tárolására szolgál - lehetővé teszi a push-értesítések fogadását. + No comment provided by engineer. + + + iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications. + Az iOS kulcstár az alkalmazás újraindítása, vagy a jelmondat módosítása után a jelmondat biztonságos tárolására szolgál - lehetővé teszi a push-értesítések fogadását. + No comment provided by engineer. + + + incognito via contact address link + inkognitó a kapcsolattartási hivatkozáson keresztül + chat list item description + + + incognito via group link + inkognitó a csoportos hivatkozáson keresztül + chat list item description + + + incognito via one-time link + inkognitó egyszer használatos hivatkozáson keresztül + chat list item description + + + indirect (%d) + közvetett (%d) + connection level description + + + invalid chat + érvénytelen csevegés + invalid chat data + + + invalid chat data + érvénytelen csevegés adat + No comment provided by engineer. + + + invalid data + érvénytelen adat + invalid chat item + + + invitation to group %@ + meghívás a(z) %@ csoportba + group name + + + invited + meghívott + No comment provided by engineer. + + + invited %@ + %@ meghívott + rcv group event chat item + + + invited to connect + meghívott, hogy csatlakozzon + chat list item title + + + invited via your group link + meghívott a csoport hivatkozásán keresztül + rcv group event chat item + + + italic + dőlt + No comment provided by engineer. + + + join as %@ + csatlakozás mint %@ + No comment provided by engineer. + + + left + elhagyta + rcv group event chat item + + + marked deleted + töröltnek jelölve + marked deleted chat item preview text + + + member + tag + member role + + + member %1$@ changed to %2$@ + %1$@ tag megváltoztatta a nevét erre: %2$@ + profile update event chat item + + + connected + kapcsolódva + rcv group event chat item + + + message received + üzenet érkezett + notification + + + minutes + perc + time unit + + + missed call + nem fogadott hívás + call status + + + moderated + moderált + moderated chat item + + + moderated by %@ + %@ által moderálva + marked deleted chat item preview text + + + months + hónap + time unit + + + never + soha + No comment provided by engineer. + + + new message + új üzenet + notification + + + no + nem + pref value + + + no e2e encryption + nincs e2e titkosítás + No comment provided by engineer. + + + no text + nincs szöveg + copied message info in history + + + observer + megfigyelő + member role + + + off + ki + enabled status + group pref value + time to disappear + + + offered %@ + %@ ajánlotta + feature offered item + + + offered %1$@: %2$@ + ajánlotta %1$@: %2$@-kor + feature offered item + + + on + be + group pref value + + + owner + tulajdonos + member role + + + peer-to-peer + ponttól-pontig + No comment provided by engineer. + + + quantum resistant e2e encryption + chat item text + + + received answer… + fogadott válasz… + No comment provided by engineer. + + + received confirmation… + visszaigazolás fogadása… + No comment provided by engineer. + + + rejected call + elutasított hívás + call status + + + removed + eltávolítva + No comment provided by engineer. + + + removed %@ + %@ eltávolítva + rcv group event chat item + + + removed contact address + törölt csatlakozási cím + profile update event chat item + + + removed profile picture + törölt profilkép + profile update event chat item + + + removed you + eltávolítottak + rcv group event chat item + + + sec + mp + network option + + + seconds + másodperc + time unit + + + secret + titok + No comment provided by engineer. + + + security code changed + biztonsági kód megváltozott + chat item text + + + send direct message + közvetlen üzenet küldése + No comment provided by engineer. + + + set new contact address + új kapcsolattartási cím beállítása + profile update event chat item + + + set new profile picture + új profilkép beállítása + profile update event chat item + + + standard end-to-end encryption + chat item text + + + starting… + indítás… + No comment provided by engineer. + + + strike + áthúzott + No comment provided by engineer. + + + this contact + ez az ismerős + notification title + + + unblocked %@ + %@ feloldva + rcv group event chat item + + + unknown + ismeretlen + connection info + + + unknown status + ismeretlen státusz + No comment provided by engineer. + + + updated group profile + módosított csoport profil + rcv group event chat item + + + updated profile + frissített profil + profile update event chat item + + + v%@ + v%@ + No comment provided by engineer. + + + v%@ (%@) + v%@ (%@) + No comment provided by engineer. + + + via contact address link + ismerős azonosítójának hivatkozásán keresztül + chat list item description + + + via group link + csoport hivatkozáson keresztül + chat list item description + + + via one-time link + egyszer használatos hivatkozáson keresztül + chat list item description + + + via relay + átjátszón keresztül + No comment provided by engineer. + + + video call (not e2e encrypted) + videóhívás (nem e2e titkosított) + No comment provided by engineer. + + + waiting for answer… + várakozás válaszra… + No comment provided by engineer. + + + waiting for confirmation… + várakozás a visszaigazolásra… + No comment provided by engineer. + + + wants to connect to you! + kapcsolatba akar lépni önnel! + No comment provided by engineer. + + + weeks + hét + time unit + + + yes + igen + pref value + + + you are invited to group + meghívást kapott a csoportba + No comment provided by engineer. + + + you are observer + megfigyelő szerep + No comment provided by engineer. + + + you blocked %@ + blokkolta őt: %@ + snd group event chat item + + + you changed address + azonosítója megváltoztatva + chat item text + + + you changed address for %@ + %@ azonosítója megváltoztatva + chat item text + + + you changed role for yourself to %@ + saját szerepkör megváltoztatva erre: %@ + snd group event chat item + + + you changed role of %1$@ to %2$@ + megváltoztatta %1$@ szerepkörét erre: %@ + snd group event chat item + + + you left + elhagyta + snd group event chat item + + + you removed %@ + eltávolította őt: %@ + snd group event chat item + + + you shared one-time link + egyszer használatos hivatkozást osztott meg + chat list item description + + + you shared one-time link incognito + egyszer használatos hivatkozást osztott meg inkognitóban + chat list item description + + + you unblocked %@ + feloldotta %@ blokkolását + snd group event chat item + + + you: + ön: + No comment provided by engineer. + + + \~strike~ + \~áthúzott~ + No comment provided by engineer. + + +
+ +
+ +
+ + + SimpleX + SimpleX + Bundle name + + + SimpleX needs camera access to scan QR codes to connect to other users and for video calls. + A SimpleX-nek kamera-hozzáférésre van szüksége a QR-kódok beolvasásához, hogy csatlakozhasson más felhasználókhoz és videohívásokhoz. + Privacy - Camera Usage Description + + + SimpleX uses Face ID for local authentication + A SimpleX Face ID-t használ a helyi hitelesítéshez + Privacy - Face ID Usage Description + + + SimpleX uses local network access to allow using user chat profile via desktop app on the same network. + 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 - Local Network Usage Description + + + SimpleX needs microphone access for audio and video calls, and to record voice messages. + A SimpleX-nek mikrofon-hozzáférésre van szüksége hang- és videohívásokhoz, valamint hangüzenetek rögzítéséhez. + Privacy - Microphone Usage Description + + + SimpleX needs access to Photo Library for saving captured and received media + A SimpleX-nek hozzáférésre van szüksége a Galériához a rögzített és fogadott média mentéséhez + Privacy - Photo Library Additions Usage Description + + +
+ +
+ +
+ + + SimpleX NSE + SimpleX NSE + Bundle display name + + + SimpleX NSE + SimpleX NSE + Bundle name + + + Copyright © 2022 SimpleX Chat. All rights reserved. + Copyright © 2022 SimpleX Chat. Minden jog fenntartva. + Copyright (human-readable) + + +
+
diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/Shared/Assets.xcassets/AccentColor.colorset/Contents.json b/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/Shared/Assets.xcassets/AccentColor.colorset/Contents.json new file mode 100644 index 0000000000..aaa7f79bc8 --- /dev/null +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/Shared/Assets.xcassets/AccentColor.colorset/Contents.json @@ -0,0 +1,23 @@ +{ + "colors" : [ + { + "color" : { + "color-space" : "srgb", + "components" : { + "red" : "0.000", + "alpha" : "1.000", + "blue" : "1.000", + "green" : "0.533" + } + }, + "idiom" : "universal" + } + ], + "properties" : { + "localizable" : true + }, + "info" : { + "version" : 1, + "author" : "xcode" + } +} \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/Shared/Assets.xcassets/Contents.json b/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/Shared/Assets.xcassets/Contents.json new file mode 100644 index 0000000000..73c00596a7 --- /dev/null +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/Shared/Assets.xcassets/Contents.json @@ -0,0 +1,6 @@ +{ + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/SimpleX NSE/en.lproj/InfoPlist.strings b/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/SimpleX NSE/en.lproj/InfoPlist.strings new file mode 100644 index 0000000000..124ddbcc33 --- /dev/null +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/SimpleX NSE/en.lproj/InfoPlist.strings @@ -0,0 +1,6 @@ +/* Bundle display name */ +"CFBundleDisplayName" = "SimpleX NSE"; +/* Bundle name */ +"CFBundleName" = "SimpleX NSE"; +/* Copyright (human-readable) */ +"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved."; diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/en.lproj/Localizable.strings b/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/en.lproj/Localizable.strings new file mode 100644 index 0000000000..cf485752ea --- /dev/null +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/en.lproj/Localizable.strings @@ -0,0 +1,30 @@ +/* No comment provided by engineer. */ +"_italic_" = "\\_italic_"; + +/* No comment provided by engineer. */ +"**Add new contact**: to create your one-time QR Code for your contact." = "**Add new contact**: to create your one-time QR Code or link for your contact."; + +/* No comment provided by engineer. */ +"*bold*" = "\\*bold*"; + +/* No comment provided by engineer. */ +"`a + b`" = "\\`a + b`"; + +/* No comment provided by engineer. */ +"~strike~" = "\\~strike~"; + +/* call status */ +"connecting call" = "connecting call…"; + +/* No comment provided by engineer. */ +"Connecting server…" = "Connecting to server…"; + +/* No comment provided by engineer. */ +"Connecting server… (error: %@)" = "Connecting to server… (error: %@)"; + +/* rcv group event chat item */ +"member connected" = "connected"; + +/* No comment provided by engineer. */ +"No group!" = "Group not found!"; + diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/en.lproj/SimpleX--iOS--InfoPlist.strings b/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/en.lproj/SimpleX--iOS--InfoPlist.strings new file mode 100644 index 0000000000..d34eb67fc7 --- /dev/null +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Source Contents/en.lproj/SimpleX--iOS--InfoPlist.strings @@ -0,0 +1,12 @@ +/* Bundle name */ +"CFBundleName" = "SimpleX"; +/* Privacy - Camera Usage Description */ +"NSCameraUsageDescription" = "SimpleX needs camera access to scan QR codes to connect to other users and for video calls."; +/* Privacy - Face ID Usage Description */ +"NSFaceIDUsageDescription" = "SimpleX uses Face ID for local authentication"; +/* Privacy - Local Network Usage Description */ +"NSLocalNetworkUsageDescription" = "SimpleX uses local network access to allow using user chat profile via desktop app on the same network."; +/* Privacy - Microphone Usage Description */ +"NSMicrophoneUsageDescription" = "SimpleX needs microphone access for audio and video calls, and to record voice messages."; +/* Privacy - Photo Library Additions Usage Description */ +"NSPhotoLibraryAddUsageDescription" = "SimpleX needs access to Photo Library for saving captured and received media"; diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/contents.json b/apps/ios/SimpleX Localizations/hu.xcloc/contents.json new file mode 100644 index 0000000000..bc788c3c10 --- /dev/null +++ b/apps/ios/SimpleX Localizations/hu.xcloc/contents.json @@ -0,0 +1,12 @@ +{ + "developmentRegion" : "en", + "project" : "SimpleX.xcodeproj", + "targetLocale" : "hu", + "toolInfo" : { + "toolBuildNumber" : "15A240d", + "toolID" : "com.apple.dt.xcode", + "toolName" : "Xcode", + "toolVersion" : "15.0" + }, + "version" : "1.0" +} \ No newline at end of file diff --git a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff index eff9291b3a..4fe1a8da8e 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -107,6 +107,11 @@ %@ si è connesso/a No comment provided by engineer.
+ + %@ downloaded + %@ scaricati + No comment provided by engineer. + %@ is connected! %@ è connesso/a! @@ -127,6 +132,11 @@ Server %@ No comment provided by engineer. + + %@ uploaded + %@ caricati + No comment provided by engineer. + %@ wants to connect! %@ si vuole connettere! @@ -342,6 +352,11 @@ **Il più privato**: non usare il server di notifica di SimpleX Chat, controlla i messaggi periodicamente in secondo piano (dipende da quanto spesso usi l'app). No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Nota bene**: usare lo stesso database su due dispositivi bloccherà la decifrazione dei messaggi dalle tue connessioni, come misura di sicurezza. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Nota bene**: NON potrai recuperare o cambiare la password se la perdi. @@ -357,6 +372,11 @@ **Attenzione**: le notifiche push istantanee richiedono una password salvata nel portachiavi. No comment provided by engineer. + + **Warning**: the archive will be removed. + **Attenzione**: l'archivio verrà rimosso. + No comment provided by engineer. + **e2e encrypted** audio call Chiamata **crittografata e2e** @@ -613,6 +633,11 @@ Il cambio di indirizzo verrà interrotto. Verrà usato il vecchio indirizzo di ricezione. No comment provided by engineer. + + Admins can block a member for all. + Gli amministratori possono bloccare un membro per tutti. + No comment provided by engineer. + Admins can create the links to join groups. Gli amministratori possono creare i link per entrare nei gruppi. @@ -668,6 +693,11 @@ Tutti i tuoi contatti resteranno connessi. L'aggiornamento del profilo verrà inviato ai tuoi contatti. No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Tutti i tuoi contatti, le conversazioni e i file verranno criptati in modo sicuro e caricati in blocchi sui relay XFTP configurati. + No comment provided by engineer. + Allow Consenti @@ -793,6 +823,11 @@ Build dell'app: %@ No comment provided by engineer. + + App data migration + Migrazione dati dell'app + No comment provided by engineer. + App encrypts new local files (except videos). L'app cripta i nuovi file locali (eccetto i video). @@ -828,6 +863,21 @@ Aspetto No comment provided by engineer. + + Apply + Applica + No comment provided by engineer. + + + Archive and upload + Archivia e carica + No comment provided by engineer. + + + Archiving database + Archiviazione del database + No comment provided by engineer. + Attach Allega @@ -1018,6 +1068,11 @@ Annulla No comment provided by engineer. + + Cancel migration + Annulla migrazione + No comment provided by engineer. + Cannot access keychain to save database password Impossibile accedere al portachiavi per salvare la password del database @@ -1119,6 +1174,11 @@ La chat è ferma. Se hai già usato questo database su un altro dispositivo, dovresti trasferirlo prima di avviare la chat. No comment provided by engineer. + + Chat migrated! + Chat migrata! + No comment provided by engineer. + Chat preferences Preferenze della chat @@ -1139,6 +1199,11 @@ Interfaccia cinese e spagnola No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + Scegli _Migra da un altro dispositivo_ sul nuovo dispositivo e scansione il codice QR. + No comment provided by engineer. + Choose file Scegli file @@ -1209,6 +1274,11 @@ Conferma aggiornamenti database No comment provided by engineer. + + Confirm network settings + Conferma le impostazioni di rete + No comment provided by engineer. + Confirm new passphrase… Conferma nuova password… @@ -1219,6 +1289,16 @@ Conferma password No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + Conferma che ricordi la password del database da migrare. + No comment provided by engineer. + + + Confirm upload + Conferma caricamento + No comment provided by engineer. + Connect Connetti @@ -1478,6 +1558,11 @@ Questo è il tuo link una tantum! Creato il %@ No comment provided by engineer. + + Creating archive link + Creazione link dell'archivio + No comment provided by engineer. + Creating link… Creazione link… @@ -1698,6 +1783,11 @@ Non è reversibile! Elimina database No comment provided by engineer. + + Delete database from this device + Elimina il database da questo dispositivo + No comment provided by engineer. + Delete file Elimina file @@ -1988,11 +2078,26 @@ Non è reversibile! Esegui downgrade e apri chat No comment provided by engineer. + + Download failed + Scaricamento fallito + No comment provided by engineer. + Download file Scarica file server test step + + Downloading archive + Scaricamento archivio + No comment provided by engineer. + + + Downloading link details + Scaricamento dettagli del link + No comment provided by engineer. + Duplicate display name! Nome da mostrare doppio! @@ -2048,6 +2153,11 @@ Non è reversibile! Attiva per tutti No comment provided by engineer. + + Enable in direct chats (BETA)! + Attivala nelle chat dirette (BETA)! + No comment provided by engineer. + Enable instant notifications? Attivare le notifiche istantanee? @@ -2163,6 +2273,11 @@ Non è reversibile! Inserisci il nome del gruppo… No comment provided by engineer. + + Enter passphrase + Inserisci password + No comment provided by engineer. + Enter passphrase… Inserisci la password… @@ -2223,6 +2338,11 @@ Non è reversibile! Errore di aggiunta membro/i No comment provided by engineer. + + Error allowing contact PQ encryption + Errore nel consentire la crittografia PQ al contatto + No comment provided by engineer. + Error changing address Errore nella modifica dell'indirizzo @@ -2313,6 +2433,11 @@ Non è reversibile! Errore nell'eliminazione del profilo utente No comment provided by engineer. + + Error downloading the archive + Errore di scaricamento dell'archivio + No comment provided by engineer. + Error enabling delivery receipts! Errore nell'attivazione delle ricevute di consegna! @@ -2388,6 +2513,11 @@ Non è reversibile! Errore nel salvataggio della password nel portachiavi No comment provided by engineer. + + Error saving settings + Errore di salvataggio delle impostazioni + when migrating + Error saving user password Errore nel salvataggio della password utente @@ -2458,6 +2588,16 @@ Non è reversibile! Errore nell'aggiornamento della privacy dell'utente No comment provided by engineer. + + Error uploading the archive + Errore di invio dell'archivio + No comment provided by engineer. + + + Error verifying passphrase: + Errore di verifica della password: + No comment provided by engineer. + Error: Errore: @@ -2508,6 +2648,11 @@ Non è reversibile! Archivio database esportato. No comment provided by engineer. + + Exported file doesn't exist + Il file esportato non esiste + No comment provided by engineer. + Exporting database archive… Esportazione archivio database… @@ -2578,6 +2723,16 @@ Non è reversibile! Filtra le chat non lette e preferite. No comment provided by engineer. + + Finalize migration + Finalizza la migrazione + No comment provided by engineer. + + + Finalize migration on another device. + Finalizza la migrazione su un altro dispositivo. + No comment provided by engineer. + Finally, we have them! 🚀 Finalmente le abbiamo! 🚀 @@ -2868,6 +3023,11 @@ Non è reversibile! Come usare i tuoi server No comment provided by engineer. + + Hungarian interface + Interfaccia in ungherese + No comment provided by engineer. + ICE servers (one per line) Server ICE (uno per riga) @@ -2933,6 +3093,16 @@ Non è reversibile! Importa database No comment provided by engineer. + + Import failed + Importazione fallita + No comment provided by engineer. + + + Importing archive + Importazione archivio + No comment provided by engineer. + Improved message delivery Consegna dei messaggi migliorata @@ -2948,6 +3118,11 @@ Non è reversibile! Configurazione del server migliorata No comment provided by engineer. + + In order to continue, chat should be stopped. + Per continuare, la chat deve essere fermata. + No comment provided by engineer. + In reply to In risposta a @@ -3060,6 +3235,11 @@ Non è reversibile! Link non valido No comment provided by engineer. + + Invalid migration confirmation + Conferma di migrazione non valida + No comment provided by engineer. + Invalid name! Nome non valido! @@ -3428,6 +3608,11 @@ Questo è il tuo link per il gruppo %@! Testo del messaggio No comment provided by engineer. + + Message too large + Messaggio troppo grande + No comment provided by engineer. + Messages Messaggi @@ -3443,11 +3628,56 @@ Questo è il tuo link per il gruppo %@! I messaggi da %@ verranno mostrati! 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. + I messaggi, i file e le chiamate sono protetti da **crittografia end-to-end** con perfect forward secrecy, ripudio e recupero da intrusione. + 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. + I messaggi, i file e le chiamate sono protetti da **crittografia e2e resistente al quantistico** con perfect forward secrecy, ripudio e recupero da intrusione. + No comment provided by engineer. + + + Migrate device + Migra dispositivo + No comment provided by engineer. + + + Migrate from another device + Migra da un altro dispositivo + No comment provided by engineer. + + + Migrate here + Migra qui + No comment provided by engineer. + + + Migrate to another device + Migra ad un altro dispositivo + No comment provided by engineer. + + + Migrate to another device via QR code. + Migra ad un altro dispositivo via codice QR. + No comment provided by engineer. + + + Migrating + Migrazione + No comment provided by engineer. + Migrating database archive… Migrazione archivio del database… No comment provided by engineer. + + Migration complete + Migrazione completata + No comment provided by engineer. + Migration error: Errore di migrazione: @@ -3807,6 +4037,11 @@ Questo è il tuo link per il gruppo %@! Apri gruppo No comment provided by engineer. + + Open migration to another device + Apri migrazione ad un altro dispositivo + authentication reason + Open user profiles Apri i profili utente @@ -3822,11 +4057,21 @@ Questo è il tuo link per il gruppo %@! Apertura dell'app… No comment provided by engineer. + + Or paste archive link + O incolla il link dell'archivio + No comment provided by engineer. + Or scan QR code O scansiona il codice QR No comment provided by engineer. + + Or securely share this file link + O condividi in modo sicuro questo link del file + No comment provided by engineer. + Or show this code O mostra questo codice @@ -3912,6 +4157,11 @@ Questo è il tuo link per il gruppo %@! Errore di decifrazione message decrypt error item + + Picture-in-picture calls + Chiamate picture-in-picture + No comment provided by engineer. + Please ask your contact to enable sending voice messages. Chiedi al tuo contatto di attivare l'invio dei messaggi vocali. @@ -3932,6 +4182,11 @@ Questo è il tuo link per il gruppo %@! Controlla le preferenze tue e del tuo contatto. No comment provided by engineer. + + Please confirm that network settings are correct for this device. + Conferma che le impostazioni di rete sono corrette per questo dispositivo. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3989,6 +4244,11 @@ Errore: %@ Probabilmente l'impronta del certificato nell'indirizzo del server è sbagliata server test error + + Post-quantum E2EE + E2EE post-quantistica + No comment provided by engineer. + Preserve the last message draft, with attachments. Conserva la bozza dell'ultimo messaggio, con gli allegati. @@ -4124,6 +4384,16 @@ Errore: %@ Notifiche push No comment provided by engineer. + + Push server + Server push + No comment provided by engineer. + + + Quantum resistant encryption + Crittografia resistente al quantistico + No comment provided by engineer. + Rate the app Valuta l'app @@ -4309,11 +4579,26 @@ Errore: %@ Ripetere la richiesta di connessione? No comment provided by engineer. + + Repeat download + Ripeti scaricamento + No comment provided by engineer. + + + Repeat import + Ripeti importazione + No comment provided by engineer. + Repeat join request? Ripetere la richiesta di ingresso? No comment provided by engineer. + + Repeat upload + Ripeti caricamento + No comment provided by engineer. + Reply Rispondi @@ -4414,6 +4699,11 @@ Errore: %@ Server SMP No comment provided by engineer. + + Safer groups + Gruppi più sicuri + No comment provided by engineer. + Save Salva @@ -4779,6 +5069,11 @@ Errore: %@ Imposta codice No comment provided by engineer. + + Set passphrase + Imposta password + No comment provided by engineer. + Set passphrase to export Imposta la password per esportare @@ -4834,6 +5129,11 @@ Errore: %@ Condividi con i contatti No comment provided by engineer. + + Show QR code + Mostra codice QR + No comment provided by engineer. + Show calls in phone history Mostra le chiamate nella cronologia del telefono @@ -4974,6 +5274,11 @@ Errore: %@ Ferma SimpleX authentication reason + + Stop chat + Ferma la chat + No comment provided by engineer. + Stop chat to enable database actions Ferma la chat per attivare le azioni del database @@ -5014,6 +5319,11 @@ Errore: %@ Smettere di condividere l'indirizzo? No comment provided by engineer. + + Stopping chat + Arresto della chat + No comment provided by engineer. + Submit Invia @@ -5261,6 +5571,16 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Questa azione non può essere annullata: il tuo profilo, i contatti, i messaggi e i file andranno persi in modo irreversibile. No comment provided by engineer. + + This chat is protected by end-to-end encryption. + Questa chat è protetta da crittografia end-to-end. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + Questa chat è protetta da crittografia end-to-end resistente al quantistico. + E2EE info chat item + This device name Il nome di questo dispositivo @@ -5555,11 +5875,21 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Aggiorna e apri chat No comment provided by engineer. + + Upload failed + Invio fallito + No comment provided by engineer. + Upload file Invia file server test step + + Uploading archive + Invio dell'archivio + No comment provided by engineer. + Use .onion hosts Usa gli host .onion @@ -5610,6 +5940,11 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usa il server No comment provided by engineer. + + Use the app while in the call. + Usa l'app mentre sei in chiamata. + No comment provided by engineer. + User profile Profilo utente @@ -5645,6 +5980,16 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Verifica le connessioni No comment provided by engineer. + + Verify database passphrase + Verifica password del database + No comment provided by engineer. + + + Verify passphrase + Verifica password + No comment provided by engineer. + Verify security code Verifica codice di sicurezza @@ -5657,7 +6002,7 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Via secure quantum resistant protocol. - Tramite protocollo sicuro resistente alla quantistica. + Tramite protocollo sicuro resistente al quantistico. No comment provided by engineer. @@ -5735,6 +6080,11 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e In attesa del video No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Attenzione: avviare la chat su più dispositivi non è supportato e provocherà problemi di recapito dei messaggi + No comment provided by engineer. + Warning: you may lose some data! Attenzione: potresti perdere alcuni dati! @@ -5755,6 +6105,11 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Messaggio di benvenuto No comment provided by engineer. + + Welcome message is too long + Il messaggio di benvenuto è troppo lungo + No comment provided by engineer. + What's new Novità @@ -5810,6 +6165,11 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Tu No comment provided by engineer. + + You **must not** use the same database on two devices. + **Non devi** usare lo stesso database su due dispositivi. + No comment provided by engineer. + You accepted connection Hai accettato la connessione @@ -5897,6 +6257,11 @@ Ripetere la richiesta di ingresso? Puoi attivarle più tardi nelle impostazioni di privacy e sicurezza dell'app. No comment provided by engineer. + + You can give another try. + Puoi fare un altro tentativo. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. Puoi nascondere o silenziare un profilo utente - scorrilo verso destra. @@ -6173,7 +6538,7 @@ Puoi annullare questa connessione e rimuovere il contatto (e riprovare più tard Your profile **%@** will be shared. - Il tuo profilo **%@** verrà condiviso. + Verrà condiviso il tuo profilo **%@**. No comment provided by engineer. @@ -6291,7 +6656,7 @@ I server di SimpleX non possono vedere il tuo profilo. blocked bloccato - No comment provided by engineer. + marked deleted chat item preview text blocked %@ @@ -6301,7 +6666,7 @@ I server di SimpleX non possono vedere il tuo profilo. blocked by admin bloccato dall'amministratore - blocked chat item + marked deleted chat item preview text bold @@ -6731,7 +7096,7 @@ I server di SimpleX non possono vedere il tuo profilo. moderated by %@ moderato da %@ - No comment provided by engineer. + marked deleted chat item preview text months @@ -6800,6 +7165,11 @@ I server di SimpleX non possono vedere il tuo profilo. peer-to-peer No comment provided by engineer. + + quantum resistant e2e encryption + crittografia e2e resistente al quantistico + chat item text + received answer… risposta ricevuta… @@ -6875,6 +7245,11 @@ I server di SimpleX non possono vedere il tuo profilo. impostata nuova immagine del profilo profile update event chat item + + standard end-to-end encryption + crittografia end-to-end standard + chat item text + starting… avvio… diff --git a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff index c9f93b458e..e10c39bbc3 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -89,6 +89,7 @@ %@ and %@ + %@ と %@ No comment provided by engineer. @@ -106,6 +107,10 @@ %@ 接続中 No comment provided by engineer. + + %@ downloaded + No comment provided by engineer. + %@ is connected! %@ 接続中! @@ -126,6 +131,10 @@ %@ サーバー No comment provided by engineer. + + %@ uploaded + No comment provided by engineer. + %@ wants to connect! %@ が接続を希望しています! @@ -333,6 +342,10 @@ **最もプライベート**: SimpleX Chat 通知サーバーを使用せず、バックグラウンドで定期的にメッセージをチェックします (アプリの使用頻度によって異なります)。 No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **注意**: パスフレーズを紛失すると、パスフレーズを復元または変更できなくなります。 @@ -348,6 +361,10 @@ **警告**: 即時の プッシュ通知には、キーチェーンに保存されたパスフレーズが必要です。 No comment provided by engineer. + + **Warning**: the archive will be removed. + No comment provided by engineer. + **e2e encrypted** audio call **e2e 暗号化**音声通話 @@ -596,6 +613,10 @@ アドレス変更は中止されます。古い受信アドレスが使用されます。 No comment provided by engineer. + + Admins can block a member for all. + No comment provided by engineer. + Admins can create the links to join groups. 管理者はグループの参加リンクを生成できます。 @@ -649,6 +670,10 @@ すべての連絡先は維持されます。連絡先に更新されたプロフィールを送信します。 No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + No comment provided by engineer. + Allow 許可 @@ -772,6 +797,10 @@ アプリのビルド: %@ No comment provided by engineer. + + App data migration + No comment provided by engineer. + App encrypts new local files (except videos). アプリは新しいローカルファイル(ビデオを除く)を暗号化します。 @@ -807,6 +836,18 @@ 見た目 No comment provided by engineer. + + Apply + No comment provided by engineer. + + + Archive and upload + No comment provided by engineer. + + + Archiving database + No comment provided by engineer. + Attach 添付する @@ -987,6 +1028,10 @@ 中止 No comment provided by engineer. + + Cancel migration + No comment provided by engineer. + Cannot access keychain to save database password データベースのパスワードを保存するためのキーチェーンにアクセスできません @@ -1087,6 +1132,10 @@ Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. No comment provided by engineer. + + Chat migrated! + No comment provided by engineer. + Chat preferences チャット設定 @@ -1107,6 +1156,10 @@ 中国語とスペイン語UI No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + No comment provided by engineer. + Choose file ファイルを選択 @@ -1176,6 +1229,10 @@ データベースのアップグレードを確認 No comment provided by engineer. + + Confirm network settings + No comment provided by engineer. + Confirm new passphrase… 新しいパスフレーズを確認… @@ -1186,6 +1243,14 @@ パスワードを確認 No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + No comment provided by engineer. + + + Confirm upload + No comment provided by engineer. + Connect 接続 @@ -1426,6 +1491,10 @@ This is your own one-time link! %@ によって作成されました No comment provided by engineer. + + Creating archive link + No comment provided by engineer. + Creating link… No comment provided by engineer. @@ -1641,6 +1710,10 @@ This cannot be undone! データベースを削除 No comment provided by engineer. + + Delete database from this device + No comment provided by engineer. + Delete file ファイルの削除 @@ -1925,11 +1998,23 @@ This cannot be undone! ダウングレードしてチャットを開く No comment provided by engineer. + + Download failed + No comment provided by engineer. + Download file ファイルをダウンロード server test step + + Downloading archive + No comment provided by engineer. + + + Downloading link details + No comment provided by engineer. + Duplicate display name! 表示の名前が重複してます! @@ -1984,6 +2069,10 @@ This cannot be undone! すべて有効 No comment provided by engineer. + + Enable in direct chats (BETA)! + No comment provided by engineer. + Enable instant notifications? 即時通知を有効にしますか? @@ -2095,6 +2184,10 @@ This cannot be undone! Enter group name… No comment provided by engineer. + + Enter passphrase + No comment provided by engineer. + Enter passphrase… 暗証フレーズを入力… @@ -2153,6 +2246,10 @@ This cannot be undone! メンバー追加にエラー発生 No comment provided by engineer. + + Error allowing contact PQ encryption + No comment provided by engineer. + Error changing address アドレス変更にエラー発生 @@ -2242,6 +2339,10 @@ This cannot be undone! ユーザのプロフィール削除にエラー発生 No comment provided by engineer. + + Error downloading the archive + No comment provided by engineer. + Error enabling delivery receipts! No comment provided by engineer. @@ -2315,6 +2416,10 @@ This cannot be undone! キーチェーンにパスフレーズを保存にエラー発生 No comment provided by engineer. + + Error saving settings + when migrating + Error saving user password ユーザーパスワード保存エラー @@ -2383,6 +2488,14 @@ This cannot be undone! ユーザープライバシーの更新のエラー No comment provided by engineer. + + Error uploading the archive + No comment provided by engineer. + + + Error verifying passphrase: + No comment provided by engineer. + Error: エラー : @@ -2432,6 +2545,10 @@ This cannot be undone! データベースのアーカイブをエクスポートします。 No comment provided by engineer. + + Exported file doesn't exist + No comment provided by engineer. + Exporting database archive… データベース アーカイブをエクスポートしています… @@ -2501,6 +2618,14 @@ This cannot be undone! 未読とお気に入りをフィルターします。 No comment provided by engineer. + + Finalize migration + No comment provided by engineer. + + + Finalize migration on another device. + No comment provided by engineer. + Finally, we have them! 🚀 ついに、私たちはそれらを手に入れました! 🚀 @@ -2786,6 +2911,10 @@ This cannot be undone! 自分のサーバの使い方 No comment provided by engineer. + + Hungarian interface + No comment provided by engineer. + ICE servers (one per line) ICEサーバ (1行に1サーバ) @@ -2851,6 +2980,14 @@ This cannot be undone! データベースを読み込む No comment provided by engineer. + + Import failed + No comment provided by engineer. + + + Importing archive + No comment provided by engineer. + Improved message delivery No comment provided by engineer. @@ -2865,6 +3002,10 @@ This cannot be undone! サーバ設定の向上 No comment provided by engineer. + + In order to continue, chat should be stopped. + No comment provided by engineer. + In reply to 返信先 @@ -2972,6 +3113,10 @@ This cannot be undone! Invalid link No comment provided by engineer. + + Invalid migration confirmation + No comment provided by engineer. + Invalid name! No comment provided by engineer. @@ -3326,6 +3471,10 @@ This is your link for group %@! メッセージ内容 No comment provided by engineer. + + Message too large + No comment provided by engineer. + Messages メッセージ @@ -3340,11 +3489,47 @@ This is your link for group %@! Messages from %@ will be shown! 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. + No comment provided by engineer. + + + Migrate device + No comment provided by engineer. + + + Migrate from another device + No comment provided by engineer. + + + Migrate here + No comment provided by engineer. + + + Migrate to another device + No comment provided by engineer. + + + Migrate to another device via QR code. + No comment provided by engineer. + + + Migrating + No comment provided by engineer. + Migrating database archive… データベースのアーカイブを移行しています… No comment provided by engineer. + + Migration complete + No comment provided by engineer. + Migration error: 移行エラー: @@ -3700,6 +3885,10 @@ This is your link for group %@! Open group No comment provided by engineer. + + Open migration to another device + authentication reason + Open user profiles ユーザープロフィールを開く @@ -3714,10 +3903,18 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or paste archive link + No comment provided by engineer. + Or scan QR code No comment provided by engineer. + + Or securely share this file link + No comment provided by engineer. + Or show this code No comment provided by engineer. @@ -3798,6 +3995,10 @@ This is your link for group %@! 永続的な復号化エラー message decrypt error item + + Picture-in-picture calls + No comment provided by engineer. + Please ask your contact to enable sending voice messages. 音声メッセージを有効にするように連絡相手に要求してください。 @@ -3818,6 +4019,10 @@ This is your link for group %@! あなたと連絡先の設定を確認してください。 No comment provided by engineer. + + Please confirm that network settings are correct for this device. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3873,6 +4078,10 @@ Error: %@ サーバアドレスの証明証IDが正しくないかもしれません server test error + + Post-quantum E2EE + No comment provided by engineer. + Preserve the last message draft, with attachments. 添付を含めて、下書きを保存する。 @@ -4005,6 +4214,14 @@ Error: %@ プッシュ通知 No comment provided by engineer. + + Push server + No comment provided by engineer. + + + Quantum resistant encryption + No comment provided by engineer. + Rate the app アプリを評価 @@ -4186,10 +4403,22 @@ Error: %@ Repeat connection request? No comment provided by engineer. + + Repeat download + No comment provided by engineer. + + + Repeat import + No comment provided by engineer. + Repeat join request? No comment provided by engineer. + + Repeat upload + No comment provided by engineer. + Reply 返信 @@ -4289,6 +4518,10 @@ Error: %@ SMPサーバ No comment provided by engineer. + + Safer groups + No comment provided by engineer. + Save 保存 @@ -4640,6 +4873,10 @@ Error: %@ パスコードを設定する No comment provided by engineer. + + Set passphrase + No comment provided by engineer. + Set passphrase to export 暗証フレーズを設定してからエクスポート @@ -4694,6 +4931,10 @@ Error: %@ 連絡先と共有する No comment provided by engineer. + + Show QR code + No comment provided by engineer. + Show calls in phone history 通話履歴を表示 @@ -4833,6 +5074,10 @@ Error: %@ SimpleX を停止 authentication reason + + Stop chat + No comment provided by engineer. + Stop chat to enable database actions チャットを停止してデータベースアクションを有効にします @@ -4873,6 +5118,10 @@ Error: %@ アドレスの共有を停止しますか? No comment provided by engineer. + + Stopping chat + No comment provided by engineer. + Submit 送信 @@ -5115,6 +5364,14 @@ It can happen because of some bug or when the connection is compromised.あなたのプロフィール、連絡先、メッセージ、ファイルが完全削除されます (※元に戻せません※)。 No comment provided by engineer. + + This chat is protected by end-to-end encryption. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + E2EE info chat item + This device name No comment provided by engineer. @@ -5393,11 +5650,19 @@ To connect, please ask your contact to create another connection link and check アップグレードしてチャットを開く No comment provided by engineer. + + Upload failed + No comment provided by engineer. + Upload file ファイルをアップロードする server test step + + Uploading archive + No comment provided by engineer. + Use .onion hosts .onionホストを使う @@ -5446,6 +5711,10 @@ To connect, please ask your contact to create another connection link and check サーバを使う No comment provided by engineer. + + Use the app while in the call. + No comment provided by engineer. + User profile ユーザープロフィール @@ -5478,6 +5747,14 @@ To connect, please ask your contact to create another connection link and check Verify connections No comment provided by engineer. + + Verify database passphrase + No comment provided by engineer. + + + Verify passphrase + No comment provided by engineer. + Verify security code セキュリティコードを確認 @@ -5565,6 +5842,10 @@ To connect, please ask your contact to create another connection link and check ビデオ待機中 No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + No comment provided by engineer. + Warning: you may lose some data! 警告: 一部のデータが失われる可能性があります! @@ -5585,6 +5866,10 @@ To connect, please ask your contact to create another connection link and check ウェルカムメッセージ No comment provided by engineer. + + Welcome message is too long + No comment provided by engineer. + What's new 新着情報 @@ -5638,6 +5923,10 @@ To connect, please ask your contact to create another connection link and check あなた No comment provided by engineer. + + You **must not** use the same database on two devices. + No comment provided by engineer. + You accepted connection 接続を承認しました @@ -5717,6 +6006,10 @@ Repeat join request? あとでアプリのプライバシーとセキュリティの設定から有効にすることができます。 No comment provided by engineer. + + You can give another try. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. ユーザープロファイルを右にスワイプすると、非表示またはミュートにすることができます。 @@ -6100,7 +6393,7 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 blocked - No comment provided by engineer. + marked deleted chat item preview text blocked %@ @@ -6108,7 +6401,7 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 blocked by admin - blocked chat item + marked deleted chat item preview text bold @@ -6534,7 +6827,7 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 moderated by %@ %@ によってモデレートされた - No comment provided by engineer. + marked deleted chat item preview text months @@ -6603,6 +6896,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 P2P No comment provided by engineer. + + quantum resistant e2e encryption + chat item text + received answer… 回答を受け取りました… @@ -6673,6 +6970,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 set new profile picture profile update event chat item + + standard end-to-end encryption + chat item text + starting… 接続中… diff --git a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff index 7afd35264d..61d91c8fbb 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -107,6 +107,11 @@ %@ verbonden No comment provided by engineer. + + %@ downloaded + %@ gedownload + No comment provided by engineer. + %@ is connected! %@ is verbonden! @@ -127,6 +132,11 @@ %@ servers No comment provided by engineer. + + %@ uploaded + %@ geüpload + No comment provided by engineer. + %@ wants to connect! %@ wil verbinding maken! @@ -342,6 +352,11 @@ **Meest privé**: gebruik geen SimpleX Chat-notificatie server, controleer berichten regelmatig op de achtergrond (afhankelijk van hoe vaak u de app gebruikt). No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Let op**: als u dezelfde database op twee apparaten gebruikt, wordt de decodering van berichten van uw verbindingen verbroken, als veiligheidsmaatregel. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Let op**: u kunt het wachtwoord NIET herstellen of wijzigen als u het kwijtraakt. @@ -357,6 +372,11 @@ **Waarschuwing**: voor directe push meldingen is een wachtwoord vereist dat is opgeslagen in de Keychain. No comment provided by engineer. + + **Warning**: the archive will be removed. + **Waarschuwing**: het archief wordt verwijderd. + No comment provided by engineer. + **e2e encrypted** audio call **e2e versleuteld** audio gesprek @@ -613,6 +633,11 @@ Adres wijziging wordt afgebroken. Het oude ontvangstadres wordt gebruikt. No comment provided by engineer. + + Admins can block a member for all. + Beheerders kunnen een lid voor iedereen blokkeren. + No comment provided by engineer. + Admins can create the links to join groups. Beheerders kunnen de uitnodiging links naar groepen aanmaken. @@ -668,6 +693,11 @@ Al uw contacten blijven verbonden. Profiel update wordt naar uw contacten verzonden. No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Al uw contacten, gesprekken en bestanden worden veilig gecodeerd en in delen geüpload naar geconfigureerde XFTP-relays. + No comment provided by engineer. + Allow Toestaan @@ -793,6 +823,11 @@ App build: %@ No comment provided by engineer. + + App data migration + Migratie van app-gegevens + No comment provided by engineer. + App encrypts new local files (except videos). App versleutelt nieuwe lokale bestanden (behalve video's). @@ -828,6 +863,21 @@ Uiterlijk No comment provided by engineer. + + Apply + Toepassen + No comment provided by engineer. + + + Archive and upload + Archiveren en uploaden + No comment provided by engineer. + + + Archiving database + Database archiveren + No comment provided by engineer. + Attach Bijvoegen @@ -1018,6 +1068,11 @@ Annuleren No comment provided by engineer. + + Cancel migration + Migratie annuleren + No comment provided by engineer. + Cannot access keychain to save database password Geen toegang tot de keychain om database wachtwoord op te slaan @@ -1119,6 +1174,11 @@ Chat is gestopt. Als je deze database al op een ander apparaat hebt gebruikt, moet je deze terugzetten voordat je met chatten begint. No comment provided by engineer. + + Chat migrated! + Chat gemigreerd! + No comment provided by engineer. + Chat preferences Gesprek voorkeuren @@ -1139,6 +1199,11 @@ Chinese en Spaanse interface No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + Kies _Migreren vanaf een ander apparaat_ op het nieuwe apparaat en scan de QR-code. + No comment provided by engineer. + Choose file Kies bestand @@ -1209,6 +1274,11 @@ Bevestig database upgrades No comment provided by engineer. + + Confirm network settings + Bevestig netwerk instellingen + No comment provided by engineer. + Confirm new passphrase… Bevestig nieuw wachtwoord… @@ -1219,6 +1289,16 @@ Bevestig wachtwoord No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + Bevestig dat u het wachtwoord voor de database onthoudt om deze te migreren. + No comment provided by engineer. + + + Confirm upload + Bevestig het uploaden + No comment provided by engineer. + Connect Verbind @@ -1478,6 +1558,11 @@ Dit is uw eigen eenmalige link! Gemaakt op %@ No comment provided by engineer. + + Creating archive link + Archief link maken + No comment provided by engineer. + Creating link… Link maken… @@ -1698,6 +1783,11 @@ Dit kan niet ongedaan gemaakt worden! Database verwijderen No comment provided by engineer. + + Delete database from this device + Verwijder de database van dit apparaat + No comment provided by engineer. + Delete file Verwijder bestand @@ -1988,11 +2078,26 @@ Dit kan niet ongedaan gemaakt worden! Downgraden en chat openen No comment provided by engineer. + + Download failed + Download mislukt + No comment provided by engineer. + Download file Download bestand server test step + + Downloading archive + Archief downloaden + No comment provided by engineer. + + + Downloading link details + Link gegevens downloaden + No comment provided by engineer. + Duplicate display name! Dubbele weergavenaam! @@ -2048,6 +2153,11 @@ Dit kan niet ongedaan gemaakt worden! Inschakelen voor iedereen No comment provided by engineer. + + Enable in direct chats (BETA)! + Activeer in directe chats (BETA)! + No comment provided by engineer. + Enable instant notifications? Onmiddellijke meldingen inschakelen? @@ -2163,6 +2273,11 @@ Dit kan niet ongedaan gemaakt worden! Groep naam invoeren… No comment provided by engineer. + + Enter passphrase + Voer het wachtwoord in + No comment provided by engineer. + Enter passphrase… Voer wachtwoord in… @@ -2223,6 +2338,11 @@ Dit kan niet ongedaan gemaakt worden! Fout bij het toevoegen van leden No comment provided by engineer. + + Error allowing contact PQ encryption + Fout bij het toestaan van contact PQ-versleuteling + No comment provided by engineer. + Error changing address Fout bij wijzigen van adres @@ -2313,6 +2433,11 @@ Dit kan niet ongedaan gemaakt worden! Fout bij het verwijderen van gebruikers profiel No comment provided by engineer. + + Error downloading the archive + Fout bij het downloaden van het archief + No comment provided by engineer. + Error enabling delivery receipts! Fout bij het inschakelen van ontvangst bevestiging! @@ -2388,6 +2513,11 @@ Dit kan niet ongedaan gemaakt worden! Fout bij opslaan van wachtwoord in de keychain No comment provided by engineer. + + Error saving settings + Fout bij opslaan van instellingen + when migrating + Error saving user password Fout bij opslaan gebruikers wachtwoord @@ -2458,6 +2588,16 @@ Dit kan niet ongedaan gemaakt worden! Fout bij updaten van gebruikers privacy No comment provided by engineer. + + Error uploading the archive + Fout bij het uploaden van het archief + No comment provided by engineer. + + + Error verifying passphrase: + Fout bij het verifiëren van het wachtwoord: + No comment provided by engineer. + Error: Fout: @@ -2508,6 +2648,11 @@ Dit kan niet ongedaan gemaakt worden! Geëxporteerd database archief. No comment provided by engineer. + + Exported file doesn't exist + Geëxporteerd bestand bestaat niet + No comment provided by engineer. + Exporting database archive… Database archief exporteren… @@ -2578,6 +2723,16 @@ Dit kan niet ongedaan gemaakt worden! Filter ongelezen en favoriete chats. No comment provided by engineer. + + Finalize migration + Voltooi de migratie + No comment provided by engineer. + + + Finalize migration on another device. + Voltooi de migratie op een ander apparaat. + No comment provided by engineer. + Finally, we have them! 🚀 Eindelijk, we hebben ze! 🚀 @@ -2868,6 +3023,11 @@ Dit kan niet ongedaan gemaakt worden! Hoe u uw servers gebruikt No comment provided by engineer. + + Hungarian interface + Hongaarse interface + No comment provided by engineer. + ICE servers (one per line) ICE servers (één per lijn) @@ -2933,6 +3093,16 @@ Dit kan niet ongedaan gemaakt worden! Database importeren No comment provided by engineer. + + Import failed + Importeren is mislukt + No comment provided by engineer. + + + Importing archive + Archief importeren + No comment provided by engineer. + Improved message delivery Verbeterde berichtbezorging @@ -2948,6 +3118,11 @@ Dit kan niet ongedaan gemaakt worden! Verbeterde serverconfiguratie No comment provided by engineer. + + In order to continue, chat should be stopped. + Om verder te kunnen gaan, moet de chat worden gestopt. + No comment provided by engineer. + In reply to In antwoord op @@ -3060,6 +3235,11 @@ Dit kan niet ongedaan gemaakt worden! Ongeldige link No comment provided by engineer. + + Invalid migration confirmation + Ongeldige migratie bevestiging + No comment provided by engineer. + Invalid name! Ongeldige naam! @@ -3428,6 +3608,11 @@ Dit is jouw link voor groep %@! Bericht tekst No comment provided by engineer. + + Message too large + Bericht te groot + No comment provided by engineer. + Messages Berichten @@ -3443,11 +3628,56 @@ Dit is jouw link voor groep %@! Berichten van %@ worden getoond! 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. + Berichten, bestanden en oproepen worden beschermd door **end-to-end codering** met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel. + 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. + Berichten, bestanden en oproepen worden beschermd door **kwantumbestendige e2e encryptie** met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel. + No comment provided by engineer. + + + Migrate device + Apparaat migreren + No comment provided by engineer. + + + Migrate from another device + Migreer vanaf een ander apparaat + No comment provided by engineer. + + + Migrate here + Migreer hierheen + No comment provided by engineer. + + + Migrate to another device + Migreer naar een ander apparaat + No comment provided by engineer. + + + Migrate to another device via QR code. + Migreer naar een ander apparaat via QR-code. + No comment provided by engineer. + + + Migrating + Migreren + No comment provided by engineer. + Migrating database archive… Database archief migreren… No comment provided by engineer. + + Migration complete + Migratie voltooid + No comment provided by engineer. + Migration error: Migratiefout: @@ -3807,6 +4037,11 @@ Dit is jouw link voor groep %@! Open groep No comment provided by engineer. + + Open migration to another device + Open de migratie naar een ander apparaat + authentication reason + Open user profiles Gebruikers profielen openen @@ -3822,11 +4057,21 @@ Dit is jouw link voor groep %@! App openen… No comment provided by engineer. + + Or paste archive link + Of plak de archief link + No comment provided by engineer. + Or scan QR code Of scan de QR-code No comment provided by engineer. + + Or securely share this file link + Of deel deze bestands link veilig + No comment provided by engineer. + Or show this code Of laat deze code zien @@ -3912,6 +4157,11 @@ Dit is jouw link voor groep %@! Decodering fout message decrypt error item + + Picture-in-picture calls + Beeld-in-beeld oproepen + No comment provided by engineer. + Please ask your contact to enable sending voice messages. Vraag uw contact om het verzenden van spraak berichten in te schakelen. @@ -3932,6 +4182,11 @@ Dit is jouw link voor groep %@! Controleer de uwe en uw contact voorkeuren. No comment provided by engineer. + + Please confirm that network settings are correct for this device. + Controleer of de netwerk instellingen correct zijn voor dit apparaat. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3989,6 +4244,11 @@ Fout: %@ Mogelijk is de certificaat vingerafdruk in het server adres onjuist server test error + + Post-quantum E2EE + Post-quantum E2EE + No comment provided by engineer. + Preserve the last message draft, with attachments. Bewaar het laatste berichtconcept, met bijlagen. @@ -4124,6 +4384,16 @@ Fout: %@ Push meldingen No comment provided by engineer. + + Push server + Push server + No comment provided by engineer. + + + Quantum resistant encryption + quantum bestendige encryptie + No comment provided by engineer. + Rate the app Beoordeel de app @@ -4309,11 +4579,26 @@ Fout: %@ Verbindingsverzoek herhalen? No comment provided by engineer. + + Repeat download + Herhaal het downloaden + No comment provided by engineer. + + + Repeat import + Herhaal import + No comment provided by engineer. + Repeat join request? Deelnameverzoek herhalen? No comment provided by engineer. + + Repeat upload + Herhaal het uploaden + No comment provided by engineer. + Reply Antwoord @@ -4414,6 +4699,11 @@ Fout: %@ SMP servers No comment provided by engineer. + + Safer groups + Veiligere groepen + No comment provided by engineer. + Save Opslaan @@ -4541,7 +4831,7 @@ Fout: %@ Search or paste SimpleX link - Zoek of plak een SimpleX link + Zoeken of plak een SimpleX link No comment provided by engineer. @@ -4779,6 +5069,11 @@ Fout: %@ Toegangscode instellen No comment provided by engineer. + + Set passphrase + Wachtwoord instellen + No comment provided by engineer. + Set passphrase to export Wachtwoord instellen om te exporteren @@ -4834,6 +5129,11 @@ Fout: %@ Delen met contacten No comment provided by engineer. + + Show QR code + Toon QR-code + No comment provided by engineer. + Show calls in phone history Toon oproepen in de telefoongeschiedenis @@ -4974,6 +5274,11 @@ Fout: %@ Stop SimpleX authentication reason + + Stop chat + Stop chat + No comment provided by engineer. + Stop chat to enable database actions Stop de chat om database acties mogelijk te maken @@ -5014,6 +5319,11 @@ Fout: %@ Stop met het delen van adres? No comment provided by engineer. + + Stopping chat + Chat stoppen + No comment provided by engineer. + Submit Indienen @@ -5261,6 +5571,16 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren. No comment provided by engineer. + + This chat is protected by end-to-end encryption. + Deze chat is beveiligd met end-to-end codering. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + Deze chat wordt beschermd door quantum bestendige end-to-end codering. + E2EE info chat item + This device name Deze apparaatnaam @@ -5555,11 +5875,21 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Upgrade en open chat No comment provided by engineer. + + Upload failed + Upload mislukt + No comment provided by engineer. + Upload file Upload bestand server test step + + Uploading archive + Archief uploaden + No comment provided by engineer. + Use .onion hosts Gebruik .onion-hosts @@ -5610,6 +5940,11 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Gebruik server No comment provided by engineer. + + Use the app while in the call. + Gebruik de app tijdens het gesprek. + No comment provided by engineer. + User profile Gebruikers profiel @@ -5645,6 +5980,16 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Controleer verbindingen No comment provided by engineer. + + Verify database passphrase + Controleer het wachtwoord van de database + No comment provided by engineer. + + + Verify passphrase + Controleer het wachtwoord + No comment provided by engineer. + Verify security code Controleer de beveiligingscode @@ -5735,6 +6080,11 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Wachten op video No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Waarschuwing: het starten van de chat op meerdere apparaten wordt niet ondersteund en zal leiden tot mislukte bezorging van berichten + No comment provided by engineer. + Warning: you may lose some data! Waarschuwing: u kunt sommige gegevens verliezen! @@ -5755,6 +6105,11 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Welkomst bericht No comment provided by engineer. + + Welcome message is too long + Welkomstbericht is te lang + No comment provided by engineer. + What's new Wat is er nieuw @@ -5810,6 +6165,11 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Jij No comment provided by engineer. + + You **must not** use the same database on two devices. + U **mag** niet dezelfde database op twee apparaten gebruiken. + No comment provided by engineer. + You accepted connection Je hebt de verbinding geaccepteerd @@ -5897,6 +6257,11 @@ Deelnameverzoek herhalen? U kunt ze later inschakelen via de privacy- en beveiligingsinstellingen van de app. No comment provided by engineer. + + You can give another try. + Je kunt het nog een keer proberen. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. U kunt een gebruikers profiel verbergen of dempen - veeg het naar rechts. @@ -6291,7 +6656,7 @@ SimpleX servers kunnen uw profiel niet zien. blocked geblokkeerd - No comment provided by engineer. + marked deleted chat item preview text blocked %@ @@ -6301,7 +6666,7 @@ SimpleX servers kunnen uw profiel niet zien. blocked by admin geblokkeerd door beheerder - blocked chat item + marked deleted chat item preview text bold @@ -6615,7 +6980,7 @@ SimpleX servers kunnen uw profiel niet zien. incognito via contact address link - incognito via contactadres link + incognito via contact adres link chat list item description @@ -6685,7 +7050,7 @@ SimpleX servers kunnen uw profiel niet zien. left - verlaten + is vertrokken rcv group event chat item @@ -6731,7 +7096,7 @@ SimpleX servers kunnen uw profiel niet zien. moderated by %@ gemodereerd door %@ - No comment provided by engineer. + marked deleted chat item preview text months @@ -6800,6 +7165,11 @@ SimpleX servers kunnen uw profiel niet zien. peer-to-peer No comment provided by engineer. + + quantum resistant e2e encryption + quantum bestendige e2e-codering + chat item text + received answer… antwoord gekregen… @@ -6875,6 +7245,11 @@ SimpleX servers kunnen uw profiel niet zien. nieuwe profielfoto instellen profile update event chat item + + standard end-to-end encryption + standaard end-to-end encryptie + chat item text + starting… beginnen… @@ -6927,7 +7302,7 @@ SimpleX servers kunnen uw profiel niet zien. via contact address link - via contactadres link + via contact adres link chat list item description diff --git a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff index 94e3e8901a..f4d10b0dc0 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -107,6 +107,11 @@ %@ połączony No comment provided by engineer. + + %@ downloaded + %@ pobrane + No comment provided by engineer. + %@ is connected! %@ jest połączony! @@ -127,6 +132,11 @@ %@ serwery No comment provided by engineer. + + %@ uploaded + %@ wgrane + No comment provided by engineer. + %@ wants to connect! %@ chce się połączyć! @@ -342,6 +352,11 @@ **Najbardziej prywatny**: nie korzystaj z serwera powiadomień SimpleX Chat, sprawdzaj wiadomości okresowo w tle (zależy jak często korzystasz z aplikacji). No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + *Uwaga*: używanie tej samej bazy danych na dwóch urządzeniach zepsuje odszyfrowywanie wiadomości twoich połączeń, jako zabezpieczenie. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Uwaga**: NIE będziesz w stanie odzyskać lub zmienić hasła, jeśli je stracisz. @@ -357,6 +372,11 @@ **Uwaga**: Natychmiastowe powiadomienia push wymagają hasła zapisanego w Keychain. No comment provided by engineer. + + **Warning**: the archive will be removed. + **Ostrzeżenie**: archiwum zostanie usunięte. + No comment provided by engineer. + **e2e encrypted** audio call **szyfrowane e2e** połączenie audio @@ -613,6 +633,11 @@ Zmiana adresu zostanie przerwana. Użyty zostanie stary adres odbiorczy. No comment provided by engineer. + + Admins can block a member for all. + Administratorzy mogą blokować członka dla wszystkich. + No comment provided by engineer. + Admins can create the links to join groups. Administratorzy mogą tworzyć linki do dołączania do grup. @@ -668,6 +693,11 @@ Wszystkie Twoje kontakty pozostaną połączone. Aktualizacja profilu zostanie wysłana do Twoich kontaktów. No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Wszystkie twoje kontakty, konwersacje i pliki będą bezpiecznie szyfrowane i wgrywane w kawałkach do skonfigurowanych przekaźników XFTP. + No comment provided by engineer. + Allow Pozwól @@ -793,6 +823,11 @@ Kompilacja aplikacji: %@ No comment provided by engineer. + + App data migration + Migracja danych aplikacji + No comment provided by engineer. + App encrypts new local files (except videos). Aplikacja szyfruje nowe lokalne pliki (bez filmów). @@ -828,6 +863,21 @@ Wygląd No comment provided by engineer. + + Apply + Zastosuj + No comment provided by engineer. + + + Archive and upload + Archiwizuj i prześlij + No comment provided by engineer. + + + Archiving database + Archiwizowanie bazy danych + No comment provided by engineer. + Attach Dołącz @@ -1018,6 +1068,11 @@ Anuluj No comment provided by engineer. + + Cancel migration + Anuluj migrację + No comment provided by engineer. + Cannot access keychain to save database password Nie można uzyskać dostępu do pęku kluczy, aby zapisać hasło do bazy danych @@ -1119,6 +1174,11 @@ Czat został zatrzymany. Jeśli korzystałeś już z tej bazy danych na innym urządzeniu, powinieneś przenieść ją z powrotem przed rozpoczęciem czatu. No comment provided by engineer. + + Chat migrated! + Czat zmigrowany! + No comment provided by engineer. + Chat preferences Preferencje czatu @@ -1139,6 +1199,11 @@ Chiński i hiszpański interfejs No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + Wybierz _Zmigruj z innego urządzenia_ na nowym urządzeniu i zeskanuj kod QR. + No comment provided by engineer. + Choose file Wybierz plik @@ -1209,6 +1274,11 @@ Potwierdź aktualizacje bazy danych No comment provided by engineer. + + Confirm network settings + Potwierdź ustawienia sieciowe + No comment provided by engineer. + Confirm new passphrase… Potwierdź nowe hasło… @@ -1219,6 +1289,16 @@ Potwierdź hasło No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + Potwierdź, że pamiętasz hasło do bazy danych, aby ją zmigrować. + No comment provided by engineer. + + + Confirm upload + Potwierdź wgranie + No comment provided by engineer. + Connect Połącz @@ -1478,6 +1558,11 @@ To jest twój jednorazowy link! Utworzony w dniu %@ No comment provided by engineer. + + Creating archive link + Tworzenie linku archiwum + No comment provided by engineer. + Creating link… Tworzenie linku… @@ -1698,6 +1783,11 @@ To nie może być cofnięte! Usuń bazę danych No comment provided by engineer. + + Delete database from this device + Usuń bazę danych z tego urządzenia + No comment provided by engineer. + Delete file Usuń plik @@ -1988,11 +2078,26 @@ To nie może być cofnięte! Obniż wersję i otwórz czat No comment provided by engineer. + + Download failed + Pobieranie nie udane + No comment provided by engineer. + Download file Pobierz plik server test step + + Downloading archive + Pobieranie archiwum + No comment provided by engineer. + + + Downloading link details + Pobieranie szczegółów linku + No comment provided by engineer. + Duplicate display name! Zduplikowana wyświetlana nazwa! @@ -2048,6 +2153,11 @@ To nie może być cofnięte! Włącz dla wszystkich No comment provided by engineer. + + Enable in direct chats (BETA)! + Włącz w czatach bezpośrednich (BETA)! + No comment provided by engineer. + Enable instant notifications? Włączyć natychmiastowe powiadomienia? @@ -2163,6 +2273,11 @@ To nie może być cofnięte! Wpisz nazwę grupy… No comment provided by engineer. + + Enter passphrase + Wprowadź hasło + No comment provided by engineer. + Enter passphrase… Wprowadź hasło… @@ -2223,6 +2338,11 @@ To nie może być cofnięte! Błąd dodawania członka(ów) No comment provided by engineer. + + Error allowing contact PQ encryption + Błąd pozwalania kontaktowi na szyfrowanie PQ + No comment provided by engineer. + Error changing address Błąd zmiany adresu @@ -2313,6 +2433,11 @@ To nie może być cofnięte! Błąd usuwania profilu użytkownika No comment provided by engineer. + + Error downloading the archive + Błąd pobierania archiwum + No comment provided by engineer. + Error enabling delivery receipts! Błąd włączania potwierdzeń dostawy! @@ -2388,6 +2513,11 @@ To nie może być cofnięte! Błąd zapisu hasła do pęku kluczy No comment provided by engineer. + + Error saving settings + Błąd zapisywania ustawień + when migrating + Error saving user password Błąd zapisu hasła użytkownika @@ -2458,6 +2588,16 @@ To nie może być cofnięte! Błąd aktualizacji prywatności użytkownika No comment provided by engineer. + + Error uploading the archive + Błąd wgrywania archiwum + No comment provided by engineer. + + + Error verifying passphrase: + Błąd weryfikowania hasła: + No comment provided by engineer. + Error: Błąd: @@ -2508,6 +2648,11 @@ To nie może być cofnięte! Wyeksportowane archiwum bazy danych. No comment provided by engineer. + + Exported file doesn't exist + Wyeksportowany plik nie istnieje + No comment provided by engineer. + Exporting database archive… Eksportowanie archiwum bazy danych… @@ -2578,6 +2723,16 @@ To nie może być cofnięte! Filtruj nieprzeczytane i ulubione czaty. No comment provided by engineer. + + Finalize migration + Dokończ migrację + No comment provided by engineer. + + + Finalize migration on another device. + Dokończ migrację na innym urządzeniu. + No comment provided by engineer. + Finally, we have them! 🚀 W końcu je mamy! 🚀 @@ -2868,6 +3023,11 @@ To nie może być cofnięte! Jak korzystać z Twoich serwerów No comment provided by engineer. + + Hungarian interface + Węgierski interfejs + No comment provided by engineer. + ICE servers (one per line) Serwery ICE (po jednym na linię) @@ -2933,6 +3093,16 @@ To nie może być cofnięte! Importuj bazę danych No comment provided by engineer. + + Import failed + Import nie udał się + No comment provided by engineer. + + + Importing archive + Importowanie archiwum + No comment provided by engineer. + Improved message delivery Ulepszona dostawa wiadomości @@ -2948,6 +3118,11 @@ To nie może być cofnięte! Ulepszona konfiguracja serwera No comment provided by engineer. + + In order to continue, chat should be stopped. + Aby konturować, czat musi zostać zatrzymany. + No comment provided by engineer. + In reply to W odpowiedzi na @@ -3060,6 +3235,11 @@ To nie może być cofnięte! Nieprawidłowy link No comment provided by engineer. + + Invalid migration confirmation + Nieprawidłowe potwierdzenie migracji + No comment provided by engineer. + Invalid name! Nieprawidłowa nazwa! @@ -3428,6 +3608,11 @@ To jest twój link do grupy %@! Tekst wiadomości No comment provided by engineer. + + Message too large + Wiadomość jest zbyt duża + No comment provided by engineer. + Messages Wiadomości @@ -3443,11 +3628,56 @@ To jest twój link do grupy %@! Wiadomości od %@ zostaną pokazane! No comment provided by engineer. + + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + Wiadomości, pliki i połączenia są chronione przez **szyfrowanie end-to-end** z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu. + 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. + Wiadomości, pliki i połączenia są chronione przez **kwantowo odporne szyfrowanie end-to-end** z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu. + No comment provided by engineer. + + + Migrate device + Zmigruj urządzenie + No comment provided by engineer. + + + Migrate from another device + Zmigruj z innego urządzenia + No comment provided by engineer. + + + Migrate here + Zmigruj tutaj + No comment provided by engineer. + + + Migrate to another device + Zmigruj do innego urządzenia + No comment provided by engineer. + + + Migrate to another device via QR code. + Zmigruj do innego urządzenia przez kod QR. + No comment provided by engineer. + + + Migrating + Migrowanie + No comment provided by engineer. + Migrating database archive… Migrowanie archiwum bazy danych… No comment provided by engineer. + + Migration complete + Migracja zakończona + No comment provided by engineer. + Migration error: Błąd migracji: @@ -3807,6 +4037,11 @@ To jest twój link do grupy %@! Grupa otwarta No comment provided by engineer. + + Open migration to another device + Otwórz migrację na innym urządzeniu + authentication reason + Open user profiles Otwórz profile użytkownika @@ -3822,11 +4057,21 @@ To jest twój link do grupy %@! Otwieranie aplikacji… No comment provided by engineer. + + Or paste archive link + Lub wklej link archiwum + No comment provided by engineer. + Or scan QR code Lub zeskanuj kod QR No comment provided by engineer. + + Or securely share this file link + Lub bezpiecznie udostępnij ten link pliku + No comment provided by engineer. + Or show this code Lub pokaż ten kod @@ -3912,6 +4157,11 @@ To jest twój link do grupy %@! Stały błąd odszyfrowania message decrypt error item + + Picture-in-picture calls + Połączenia obraz-w-obrazie + No comment provided by engineer. + Please ask your contact to enable sending voice messages. Poproś Twój kontakt o włączenie wysyłania wiadomości głosowych. @@ -3932,6 +4182,11 @@ To jest twój link do grupy %@! Proszę sprawdzić preferencje Twoje i Twojego kontaktu. No comment provided by engineer. + + Please confirm that network settings are correct for this device. + Proszę potwierdzić, że ustawienia sieciowe są prawidłowe dla tego urządzenia. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3989,6 +4244,11 @@ Błąd: %@ Możliwe, że odcisk palca certyfikatu w adresie serwera jest nieprawidłowy server test error + + Post-quantum E2EE + Postkwantowe E2EE + No comment provided by engineer. + Preserve the last message draft, with attachments. Zachowaj ostatnią wersję roboczą wiadomości wraz z załącznikami. @@ -4124,6 +4384,16 @@ Błąd: %@ Powiadomienia push No comment provided by engineer. + + Push server + Serwer Push + No comment provided by engineer. + + + Quantum resistant encryption + Kwantowo odporne szyfrowanie + No comment provided by engineer. + Rate the app Oceń aplikację @@ -4309,11 +4579,26 @@ Błąd: %@ Powtórzyć prośbę połączenia? No comment provided by engineer. + + Repeat download + Powtórz pobieranie + No comment provided by engineer. + + + Repeat import + Powtórz importowanie + No comment provided by engineer. + Repeat join request? Powtórzyć prośbę dołączenia? No comment provided by engineer. + + Repeat upload + Powtórz wgrywanie + No comment provided by engineer. + Reply Odpowiedz @@ -4414,6 +4699,11 @@ Błąd: %@ Serwery SMP No comment provided by engineer. + + Safer groups + Bezpieczniejsze grupy + No comment provided by engineer. + Save Zapisz @@ -4779,6 +5069,11 @@ Błąd: %@ Ustaw pin No comment provided by engineer. + + Set passphrase + Ustaw hasło + No comment provided by engineer. + Set passphrase to export Ustaw hasło do eksportu @@ -4834,6 +5129,11 @@ Błąd: %@ Udostępnij kontaktom No comment provided by engineer. + + Show QR code + Pokaż kod QR + No comment provided by engineer. + Show calls in phone history Pokaż połączenia w historii telefonu @@ -4974,6 +5274,11 @@ Błąd: %@ Zatrzymaj SimpleX authentication reason + + Stop chat + Zatrzymaj czat + No comment provided by engineer. + Stop chat to enable database actions Zatrzymaj czat, aby umożliwić działania na bazie danych @@ -5014,6 +5319,11 @@ Błąd: %@ Przestać udostępniać adres? No comment provided by engineer. + + Stopping chat + Zatrzymywanie czatu + No comment provided by engineer. + Submit Zatwierdź @@ -5261,6 +5571,16 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Tego działania nie można cofnąć - Twój profil, kontakty, wiadomości i pliki zostaną nieodwracalnie utracone. No comment provided by engineer. + + This chat is protected by end-to-end encryption. + Ten czat jest chroniony przez szyfrowanie end-to-end. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + Ten czat jest chroniony przez kwantowo odporne szyfrowanie end-to-end. + E2EE info chat item + This device name Nazwa tego urządzenia @@ -5555,11 +5875,21 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Zaktualizuj i otwórz czat No comment provided by engineer. + + Upload failed + Wgrywanie nie udane + No comment provided by engineer. + Upload file Prześlij plik server test step + + Uploading archive + Wgrywanie archiwum + No comment provided by engineer. + Use .onion hosts Użyj hostów .onion @@ -5610,6 +5940,11 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Użyj serwera No comment provided by engineer. + + Use the app while in the call. + Używaj aplikacji podczas połączenia. + No comment provided by engineer. + User profile Profil użytkownika @@ -5645,6 +5980,16 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Zweryfikuj połączenia No comment provided by engineer. + + Verify database passphrase + Zweryfikuj hasło bazy danych + No comment provided by engineer. + + + Verify passphrase + Zweryfikuj hasło + No comment provided by engineer. + Verify security code Weryfikuj kod bezpieczeństwa @@ -5735,6 +6080,11 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Oczekiwanie na film No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Ostrzeżenie: rozpoczęcie czatu na wielu urządzeniach nie jest wspierane i spowoduje niepowodzenia dostarczania wiadomości + No comment provided by engineer. + Warning: you may lose some data! Uwaga: możesz stracić niektóre dane! @@ -5755,6 +6105,11 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Wiadomość powitalna No comment provided by engineer. + + Welcome message is too long + Wiadomość powitalna jest zbyt długa + No comment provided by engineer. + What's new Co nowego @@ -5810,6 +6165,11 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Ty No comment provided by engineer. + + You **must not** use the same database on two devices. + **Nie możesz** używać tej samej bazy na dwóch urządzeniach. + No comment provided by engineer. + You accepted connection Zaakceptowałeś połączenie @@ -5897,6 +6257,11 @@ Powtórzyć prośbę dołączenia? Możesz je włączyć później w ustawieniach Prywatności i Bezpieczeństwa aplikacji. No comment provided by engineer. + + You can give another try. + Możesz spróbować ponownie. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. Możesz ukryć lub wyciszyć profil użytkownika - przesuń palcem w prawo. @@ -6291,7 +6656,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. blocked zablokowany - No comment provided by engineer. + marked deleted chat item preview text blocked %@ @@ -6301,7 +6666,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. blocked by admin zablokowany przez admina - blocked chat item + marked deleted chat item preview text bold @@ -6731,7 +7096,7 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. moderated by %@ moderowany przez %@ - No comment provided by engineer. + marked deleted chat item preview text months @@ -6800,6 +7165,11 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. peer-to-peer No comment provided by engineer. + + quantum resistant e2e encryption + kwantowo odporne szyfrowanie e2e + chat item text + received answer… otrzymano odpowiedź… @@ -6875,6 +7245,11 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. ustaw nowe zdjęcie profilu profile update event chat item + + standard end-to-end encryption + standardowe szyfrowanie end-to-end + chat item text + starting… uruchamianie… diff --git a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff index 540a4eada4..b4fa69449f 100644 --- a/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff +++ b/apps/ios/SimpleX Localizations/pt-BR.xcloc/Localized Contents/pt-BR.xliff @@ -69,7 +69,7 @@ %@ is not verified - %@ não foi verificado + %@ não foi verificado(a) No comment provided by engineer. @@ -84,12 +84,12 @@ %d days - %d dia(s) + %d dias message ttl %d hours - %d hora(s) + %d horas message ttl @@ -99,7 +99,7 @@ %d months - %d mês(es) + %d meses message ttl @@ -109,7 +109,7 @@ %d skipped message(s) - %d mensagem(ns) ignorada(s) + %d mensagem(ns) omitida(s) integrity error chat item @@ -5189,6 +5189,51 @@ Isso pode acontecer por causa de algum bug ou quando a conexão está comprometi Desaparecerá: %@ copied message info + + # %@ + # %@ + copied message info title, # <title> + + + ## History + ## Histórico + copied message info + + + ## In reply to + ## Em resposta a + copied message info + + + %@ connected + %@ conectado(a) + No comment provided by engineer. + + + %@, %@ and %lld members + %@, %@ e %lld membros + No comment provided by engineer. + + + %@ and %@ connected + %@ e %@ conectados + No comment provided by engineer. + + + %@, %@ and %lld other members connected + %@, %@ e %lld e outros membros se conectaram + No comment provided by engineer. + + + %@ and %@ + %@ e %@ + No comment provided by engineer. + + + %1$@ at %2$@: + %1$@ em %2$@: + copied message info, <sender> at <time> + diff --git a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff index b0d1a0cd24..f78bdd0384 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -107,6 +107,11 @@ %@ соединен(а) No comment provided by engineer. + + %@ downloaded + %@ загружено + No comment provided by engineer. + %@ is connected! Установлено соединение с %@! @@ -127,6 +132,11 @@ %@ серверы No comment provided by engineer. + + %@ uploaded + %@ загружено + No comment provided by engineer. + %@ wants to connect! %@ хочет соединиться! @@ -342,6 +352,11 @@ **Самый конфиденциальный**: не использовать сервер уведомлений SimpleX Chat, проверять сообщения периодически в фоновом режиме (зависит от того насколько часто Вы используете приложение). No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Обратите внимание**: использование одной и той же базы данных на двух устройствах нарушит расшифровку сообщений от ваших контактов, как свойство защиты соединений. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Внимание**: Вы не сможете восстановить или поменять пароль, если Вы его потеряете. @@ -357,6 +372,11 @@ **Внимание**: для работы мгновенных уведомлений пароль должен быть сохранен в Keychain. No comment provided by engineer. + + **Warning**: the archive will be removed. + **Внимание**: архив будет удален. + No comment provided by engineer. + **e2e encrypted** audio call **e2e зашифрованный** аудиозвонок @@ -613,6 +633,11 @@ Изменение адреса будет прекращено. Будет использоваться старый адрес. No comment provided by engineer. + + Admins can block a member for all. + Админы могут заблокировать члена группы. + No comment provided by engineer. + Admins can create the links to join groups. Админы могут создать ссылки для вступления в группу. @@ -668,6 +693,11 @@ Все Ваши контакты сохранятся. Обновленный профиль будет отправлен Вашим контактам. No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Все ваши контакты, разговоры и файлы будут надежно зашифрованы и загружены на выбранные XFTP серверы. + No comment provided by engineer. + Allow Разрешить @@ -793,6 +823,11 @@ Сборка приложения: %@ No comment provided by engineer. + + App data migration + Миграция данных + No comment provided by engineer. + App encrypts new local files (except videos). Приложение шифрует новые локальные файлы (кроме видео). @@ -828,6 +863,21 @@ Интерфейс No comment provided by engineer. + + Apply + Применить + No comment provided by engineer. + + + Archive and upload + Архивировать и загрузить + No comment provided by engineer. + + + Archiving database + Подготовка архива + No comment provided by engineer. + Attach Прикрепить @@ -1018,6 +1068,11 @@ Отменить No comment provided by engineer. + + Cancel migration + Отменить миграцию + No comment provided by engineer. + Cannot access keychain to save database password Ошибка доступа к Keychain при сохранении пароля @@ -1119,6 +1174,11 @@ Чат остановлен. Если вы уже использовали эту базу данных на другом устройстве, перенесите ее обратно до запуска чата. No comment provided by engineer. + + Chat migrated! + Чат мигрирован! + No comment provided by engineer. + Chat preferences Предпочтения @@ -1139,6 +1199,11 @@ Китайский и Испанский интерфейс No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + Выберите _Мигрировать с другого устройства_ на новом устройстве и сосканируйте QR код. + No comment provided by engineer. + Choose file Выбрать файл @@ -1209,6 +1274,11 @@ Подтвердить обновление базы данных No comment provided by engineer. + + Confirm network settings + Подтвердите настройки сети + No comment provided by engineer. + Confirm new passphrase… Подтвердите новый пароль… @@ -1219,6 +1289,16 @@ Подтвердить пароль No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + Подтвердите, что Вы помните пароль базы данных для ее миграции. + No comment provided by engineer. + + + Confirm upload + Подтвердить загрузку + No comment provided by engineer. + Connect Соединиться @@ -1478,6 +1558,11 @@ This is your own one-time link! Дата создания %@ No comment provided by engineer. + + Creating archive link + Создание ссылки на архив + No comment provided by engineer. + Creating link… Создаётся ссылка… @@ -1698,6 +1783,11 @@ This cannot be undone! Удалить данные чата No comment provided by engineer. + + Delete database from this device + Удалить базу данных с этого устройства + No comment provided by engineer. + Delete file Удалить файл @@ -1988,11 +2078,26 @@ This cannot be undone! Откатить версию и открыть чат No comment provided by engineer. + + Download failed + Ошибка загрузки + No comment provided by engineer. + Download file Загрузка файла server test step + + Downloading archive + Загрузка архива + No comment provided by engineer. + + + Downloading link details + Загрузка ссылки архива + No comment provided by engineer. + Duplicate display name! Имя профиля уже используется! @@ -2048,6 +2153,11 @@ This cannot be undone! Включить для всех No comment provided by engineer. + + Enable in direct chats (BETA)! + Включите для контактов (BETA)! + No comment provided by engineer. + Enable instant notifications? Включить мгновенные уведомления? @@ -2163,6 +2273,11 @@ This cannot be undone! Введите имя группы… No comment provided by engineer. + + Enter passphrase + Введите пароль + No comment provided by engineer. + Enter passphrase… Введите пароль… @@ -2223,6 +2338,11 @@ This cannot be undone! Ошибка при добавлении членов группы No comment provided by engineer. + + Error allowing contact PQ encryption + Ошибка разрешения квантово-устойчивого шифрования + No comment provided by engineer. + Error changing address Ошибка при изменении адреса @@ -2313,6 +2433,11 @@ This cannot be undone! Ошибка удаления профиля пользователя No comment provided by engineer. + + Error downloading the archive + Ошибка загрузки архива + No comment provided by engineer. + Error enabling delivery receipts! Ошибка при включении отчётов о доставке! @@ -2388,6 +2513,11 @@ This cannot be undone! Ошибка сохранения пароля в Keychain No comment provided by engineer. + + Error saving settings + Ошибка сохранения настроек + when migrating + Error saving user password Ошибка при сохранении пароля пользователя @@ -2458,6 +2588,16 @@ This cannot be undone! Ошибка при обновлении конфиденциальности No comment provided by engineer. + + Error uploading the archive + Ошибка загрузки архива + No comment provided by engineer. + + + Error verifying passphrase: + Ошибка подтверждения пароля: + No comment provided by engineer. + Error: Ошибка: @@ -2508,6 +2648,11 @@ This cannot be undone! Архив чата экспортирован. No comment provided by engineer. + + Exported file doesn't exist + Экспортированный файл не существует + No comment provided by engineer. + Exporting database archive… Архив чата экспортируется… @@ -2578,6 +2723,16 @@ This cannot be undone! Фильтровать непрочитанные и избранные чаты. No comment provided by engineer. + + Finalize migration + Завершить миграцию + No comment provided by engineer. + + + Finalize migration on another device. + Завершите миграцию на другом устройстве. + No comment provided by engineer. + Finally, we have them! 🚀 Наконец-то, мы их добавили! 🚀 @@ -2868,6 +3023,11 @@ This cannot be undone! Как использовать серверы No comment provided by engineer. + + Hungarian interface + Венгерский интерфейс + No comment provided by engineer. + ICE servers (one per line) ICE серверы (один на строке) @@ -2933,6 +3093,16 @@ This cannot be undone! Импорт архива чата No comment provided by engineer. + + Import failed + Ошибка импорта + No comment provided by engineer. + + + Importing archive + Импорт архива + No comment provided by engineer. + Improved message delivery Улучшенная доставка сообщений @@ -2948,6 +3118,11 @@ This cannot be undone! Улучшенная конфигурация серверов No comment provided by engineer. + + In order to continue, chat should be stopped. + Чтобы продолжить, чат должен быть остановлен. + No comment provided by engineer. + In reply to В ответ на @@ -3060,6 +3235,11 @@ This cannot be undone! Ошибка ссылки No comment provided by engineer. + + Invalid migration confirmation + Ошибка подтверждения миграции + No comment provided by engineer. + Invalid name! Неверное имя! @@ -3428,6 +3608,11 @@ This is your link for group %@! Текст сообщения No comment provided by engineer. + + Message too large + Сообщение слишком большое + No comment provided by engineer. + Messages Сообщения @@ -3443,11 +3628,56 @@ This is your link for group %@! Сообщения от %@ будут показаны! No comment provided by engineer. + + Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. + Сообщения, файлы и звонки защищены **end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома. + 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. + Сообщения, файлы и звонки защищены **квантово-устойчивым end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома. + No comment provided by engineer. + + + Migrate device + Мигрировать устройство + No comment provided by engineer. + + + Migrate from another device + Миграция с другого устройства + No comment provided by engineer. + + + Migrate here + Мигрировать сюда + No comment provided by engineer. + + + Migrate to another device + Мигрировать на другое устройство + No comment provided by engineer. + + + Migrate to another device via QR code. + Мигрируйте на другое устройство через QR код. + No comment provided by engineer. + + + Migrating + Миграция + No comment provided by engineer. + Migrating database archive… Данные чата перемещаются… No comment provided by engineer. + + Migration complete + Миграция завершена + No comment provided by engineer. + Migration error: Ошибка при перемещении данных: @@ -3807,6 +4037,11 @@ This is your link for group %@! Открыть группу No comment provided by engineer. + + Open migration to another device + Открытие миграции на другое устройство + authentication reason + Open user profiles Открыть профили пользователя @@ -3822,11 +4057,21 @@ This is your link for group %@! Приложение отрывается… No comment provided by engineer. + + Or paste archive link + Или вставьте ссылку архива + No comment provided by engineer. + Or scan QR code Или отсканируйте QR код No comment provided by engineer. + + Or securely share this file link + Или передайте эту ссылку + No comment provided by engineer. + Or show this code Или покажите этот код @@ -3912,6 +4157,11 @@ This is your link for group %@! Ошибка расшифровки message decrypt error item + + Picture-in-picture calls + Звонки с картинкой-в-картинке + No comment provided by engineer. + Please ask your contact to enable sending voice messages. Попросите у Вашего контакта разрешить отправку голосовых сообщений. @@ -3932,6 +4182,11 @@ This is your link for group %@! Проверьте предпочтения Вашего контакта. No comment provided by engineer. + + Please confirm that network settings are correct for this device. + Пожалуйста, подтвердите, что настройки сети верны для этого устройства. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3989,6 +4244,11 @@ Error: %@ Возможно, хэш сертификата в адресе сервера неверный server test error + + Post-quantum E2EE + Квантово-устойчивое E2EE + No comment provided by engineer. + Preserve the last message draft, with attachments. Сохранить последний черновик, вместе с вложениями. @@ -4124,6 +4384,16 @@ Error: %@ Доставка уведомлений No comment provided by engineer. + + Push server + Сервер уведомлений + No comment provided by engineer. + + + Quantum resistant encryption + Квантово-устойчивое шифрование + No comment provided by engineer. + Rate the app Оценить приложение @@ -4309,11 +4579,26 @@ Error: %@ Повторить запрос на соединение? No comment provided by engineer. + + Repeat download + Повторить загрузку + No comment provided by engineer. + + + Repeat import + Повторить импорт + No comment provided by engineer. + Repeat join request? Повторить запрос на вступление? No comment provided by engineer. + + Repeat upload + Повторить загрузку + No comment provided by engineer. + Reply Ответить @@ -4414,6 +4699,11 @@ Error: %@ SMP серверы No comment provided by engineer. + + Safer groups + Более безопасные группы + No comment provided by engineer. + Save Сохранить @@ -4779,6 +5069,11 @@ Error: %@ Установить код доступа No comment provided by engineer. + + Set passphrase + Установить пароль + No comment provided by engineer. + Set passphrase to export Установите пароль @@ -4834,6 +5129,11 @@ Error: %@ Поделиться с контактами No comment provided by engineer. + + Show QR code + Показать QR код + No comment provided by engineer. + Show calls in phone history Показать звонки в истории телефона @@ -4974,6 +5274,11 @@ Error: %@ Остановить SimpleX authentication reason + + Stop chat + Остановить чат + No comment provided by engineer. + Stop chat to enable database actions Остановите чат, чтобы разблокировать операции с архивом чата @@ -5014,6 +5319,11 @@ Error: %@ Прекратить делиться адресом? No comment provided by engineer. + + Stopping chat + Остановка чата + No comment provided by engineer. + Submit Продолжить @@ -5261,6 +5571,16 @@ It can happen because of some bug or when the connection is compromised.Это действие нельзя отменить — Ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны. No comment provided by engineer. + + This chat is protected by end-to-end encryption. + Чат защищен end-to-end шифрованием. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + Чат защищен квантово-устойчивым end-to-end шифрованием. + E2EE info chat item + This device name Имя этого устройства @@ -5555,11 +5875,21 @@ To connect, please ask your contact to create another connection link and check Обновить и открыть чат No comment provided by engineer. + + Upload failed + Ошибка загрузки + No comment provided by engineer. + Upload file Загрузка файла server test step + + Uploading archive + Загрузка архива + No comment provided by engineer. + Use .onion hosts Использовать .onion хосты @@ -5610,6 +5940,11 @@ To connect, please ask your contact to create another connection link and check Использовать сервер No comment provided by engineer. + + Use the app while in the call. + Используйте приложение во время звонка. + No comment provided by engineer. + User profile Профиль чата @@ -5645,6 +5980,16 @@ To connect, please ask your contact to create another connection link and check Проверять соединения No comment provided by engineer. + + Verify database passphrase + Проверка пароля базы данных + No comment provided by engineer. + + + Verify passphrase + Проверить пароль + No comment provided by engineer. + Verify security code Подтвердить код безопасности @@ -5735,6 +6080,11 @@ To connect, please ask your contact to create another connection link and check Ожидание видео No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + Внимание: запуск чата на нескольких устройствах не поддерживается и приведет к сбоям доставки сообщений. + No comment provided by engineer. + Warning: you may lose some data! Предупреждение: Вы можете потерять какие то данные! @@ -5755,6 +6105,11 @@ To connect, please ask your contact to create another connection link and check Приветственное сообщение No comment provided by engineer. + + Welcome message is too long + Приветственное сообщение слишком длинное + No comment provided by engineer. + What's new Новые функции @@ -5810,6 +6165,11 @@ To connect, please ask your contact to create another connection link and check Вы No comment provided by engineer. + + You **must not** use the same database on two devices. + Вы **не должны** использовать одну и ту же базу данных на двух устройствах. + No comment provided by engineer. + You accepted connection Вы приняли приглашение соединиться @@ -5897,6 +6257,11 @@ Repeat join request? Вы можете включить их позже в настройках Конфиденциальности. No comment provided by engineer. + + You can give another try. + Вы можете попробовать еще раз. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. Вы можете скрыть профиль или выключить уведомления - потяните его вправо. @@ -6291,7 +6656,7 @@ SimpleX серверы не могут получить доступ к Ваше blocked заблокировано - No comment provided by engineer. + marked deleted chat item preview text blocked %@ @@ -6301,7 +6666,7 @@ SimpleX серверы не могут получить доступ к Ваше blocked by admin заблокировано администратором - blocked chat item + marked deleted chat item preview text bold @@ -6731,7 +7096,7 @@ SimpleX серверы не могут получить доступ к Ваше moderated by %@ удалено %@ - No comment provided by engineer. + marked deleted chat item preview text months @@ -6800,6 +7165,11 @@ SimpleX серверы не могут получить доступ к Ваше peer-to-peer No comment provided by engineer. + + quantum resistant e2e encryption + квантово-устойчивое e2e шифрование + chat item text + received answer… получен ответ… @@ -6875,6 +7245,11 @@ SimpleX серверы не могут получить доступ к Ваше установлена новая картинка профиля profile update event chat item + + standard end-to-end encryption + стандартное end-to-end шифрование + chat item text + starting… инициализация… diff --git a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff index 5e0659e935..e1b0e4d3e4 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -101,6 +101,10 @@ %@ connected No comment provided by engineer. + + %@ downloaded + No comment provided by engineer. + %@ is connected! %@ เชื่อมต่อสำเร็จ! @@ -121,6 +125,10 @@ %@ เซิร์ฟเวอร์ No comment provided by engineer. + + %@ uploaded + No comment provided by engineer. + %@ wants to connect! %@ อยากเชื่อมต่อ! @@ -324,6 +332,10 @@ **ส่วนตัวที่สุด**: ไม่ใช้เซิร์ฟเวอร์การแจ้งเตือนของ SimpleX Chat ตรวจสอบข้อความเป็นระยะในพื้นหลัง (ขึ้นอยู่กับความถี่ที่คุณใช้แอป) No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **โปรดทราบ**: คุณจะไม่สามารถกู้คืนหรือเปลี่ยนรหัสผ่านได้หากคุณทำรหัสผ่านหาย @@ -339,6 +351,10 @@ **คำเตือน**: การแจ้งเตือนแบบพุชทันทีจำเป็นต้องบันทึกรหัสผ่านไว้ใน Keychain No comment provided by engineer. + + **Warning**: the archive will be removed. + No comment provided by engineer. + **e2e encrypted** audio call การโทรเสียงแบบ **encrypted จากต้นจนจบ** @@ -585,6 +601,10 @@ การเปลี่ยนแปลงที่อยู่จะถูกยกเลิก จะใช้ที่อยู่เก่าของผู้รับ No comment provided by engineer. + + Admins can block a member for all. + No comment provided by engineer. + Admins can create the links to join groups. ผู้ดูแลระบบสามารถสร้างลิงก์เพื่อเข้าร่วมกลุ่มต่างๆได้ @@ -638,6 +658,10 @@ ผู้ติดต่อทั้งหมดของคุณจะยังคงเชื่อมต่ออยู่. ผู้ติดต่อทั้งหมดของคุณจะยังคงเชื่อมต่ออยู่ การอัปเดตโปรไฟล์จะถูกส่งไปยังผู้ติดต่อของคุณ. No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + No comment provided by engineer. + Allow อนุญาต @@ -761,6 +785,10 @@ รุ่นแอป: %@ No comment provided by engineer. + + App data migration + No comment provided by engineer. + App encrypts new local files (except videos). No comment provided by engineer. @@ -795,6 +823,18 @@ รูปร่างลักษณะ No comment provided by engineer. + + Apply + No comment provided by engineer. + + + Archive and upload + No comment provided by engineer. + + + Archiving database + No comment provided by engineer. + Attach แนบ @@ -974,6 +1014,10 @@ ยกเลิก No comment provided by engineer. + + Cancel migration + No comment provided by engineer. + Cannot access keychain to save database password ไม่สามารถเข้าถึง keychain เพื่อบันทึกรหัสผ่านฐานข้อมูล @@ -1074,6 +1118,10 @@ Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. No comment provided by engineer. + + Chat migrated! + No comment provided by engineer. + Chat preferences ค่ากําหนดในการแชท @@ -1094,6 +1142,10 @@ อินเทอร์เฟซภาษาจีนและสเปน No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + No comment provided by engineer. + Choose file เลือกไฟล์ @@ -1163,6 +1215,10 @@ ยืนยันการอัพเกรดฐานข้อมูล No comment provided by engineer. + + Confirm network settings + No comment provided by engineer. + Confirm new passphrase… ยืนยันรหัสผ่านใหม่… @@ -1173,6 +1229,14 @@ ยืนยันรหัสผ่าน No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + No comment provided by engineer. + + + Confirm upload + No comment provided by engineer. + Connect เชื่อมต่อ @@ -1410,6 +1474,10 @@ This is your own one-time link! สร้างเมื่อ %@ No comment provided by engineer. + + Creating archive link + No comment provided by engineer. + Creating link… No comment provided by engineer. @@ -1625,6 +1693,10 @@ This cannot be undone! ลบฐานข้อมูล No comment provided by engineer. + + Delete database from this device + No comment provided by engineer. + Delete file ลบไฟล์ @@ -1907,11 +1979,23 @@ This cannot be undone! ปรับลดรุ่นและเปิดแชท No comment provided by engineer. + + Download failed + No comment provided by engineer. + Download file ดาวน์โหลดไฟล์ server test step + + Downloading archive + No comment provided by engineer. + + + Downloading link details + No comment provided by engineer. + Duplicate display name! ชื่อที่แสดงซ้ำ! @@ -1966,6 +2050,10 @@ This cannot be undone! เปิดใช้งานสําหรับทุกคน No comment provided by engineer. + + Enable in direct chats (BETA)! + No comment provided by engineer. + Enable instant notifications? เปิดใช้งานการแจ้งเตือนทันที? @@ -2075,6 +2163,10 @@ This cannot be undone! Enter group name… No comment provided by engineer. + + Enter passphrase + No comment provided by engineer. + Enter passphrase… ใส่รหัสผ่าน @@ -2133,6 +2225,10 @@ This cannot be undone! เกิดข้อผิดพลาดในการเพิ่มสมาชิก No comment provided by engineer. + + Error allowing contact PQ encryption + No comment provided by engineer. + Error changing address เกิดข้อผิดพลาดในการเปลี่ยนที่อยู่ @@ -2220,6 +2316,10 @@ This cannot be undone! เกิดข้อผิดพลาดในการลบโปรไฟล์ผู้ใช้ No comment provided by engineer. + + Error downloading the archive + No comment provided by engineer. + Error enabling delivery receipts! เกิดข้อผิดพลาดในการเปิดใช้ใบเสร็จการจัดส่ง! @@ -2294,6 +2394,10 @@ This cannot be undone! เกิดข้อผิดพลาดในการบันทึกรหัสผ่านไปยัง keychain No comment provided by engineer. + + Error saving settings + when migrating + Error saving user password เกิดข้อผิดพลาดในการบันทึกรหัสผ่านผู้ใช้ @@ -2362,6 +2466,14 @@ This cannot be undone! เกิดข้อผิดพลาดในการอัปเดตข้อมูลส่วนตัวของผู้ใช้ No comment provided by engineer. + + Error uploading the archive + No comment provided by engineer. + + + Error verifying passphrase: + No comment provided by engineer. + Error: ผิดพลาด: @@ -2411,6 +2523,10 @@ This cannot be undone! ที่เก็บถาวรฐานข้อมูลที่ส่งออก No comment provided by engineer. + + Exported file doesn't exist + No comment provided by engineer. + Exporting database archive… กำลังส่งออกที่เก็บถาวรฐานข้อมูล… @@ -2480,6 +2596,14 @@ This cannot be undone! กรองแชทที่ยังไม่อ่านและแชทโปรด No comment provided by engineer. + + Finalize migration + No comment provided by engineer. + + + Finalize migration on another device. + No comment provided by engineer. + Finally, we have them! 🚀 ในที่สุดเราก็มีแล้ว! 🚀 @@ -2765,6 +2889,10 @@ This cannot be undone! วิธีใช้เซิร์ฟเวอร์ของคุณ No comment provided by engineer. + + Hungarian interface + No comment provided by engineer. + ICE servers (one per line) เซิร์ฟเวอร์ ICE (หนึ่งเครื่องต่อสาย) @@ -2830,6 +2958,14 @@ This cannot be undone! นำเข้าฐานข้อมูล No comment provided by engineer. + + Import failed + No comment provided by engineer. + + + Importing archive + No comment provided by engineer. + Improved message delivery No comment provided by engineer. @@ -2844,6 +2980,10 @@ This cannot be undone! ปรับปรุงการกําหนดค่าเซิร์ฟเวอร์แล้ว No comment provided by engineer. + + In order to continue, chat should be stopped. + No comment provided by engineer. + In reply to ในการตอบกลับถึง @@ -2950,6 +3090,10 @@ This cannot be undone! Invalid link No comment provided by engineer. + + Invalid migration confirmation + No comment provided by engineer. + Invalid name! No comment provided by engineer. @@ -3304,6 +3448,10 @@ This is your link for group %@! ข้อความ No comment provided by engineer. + + Message too large + No comment provided by engineer. + Messages ข้อความ @@ -3318,11 +3466,47 @@ This is your link for group %@! Messages from %@ will be shown! 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. + No comment provided by engineer. + + + Migrate device + No comment provided by engineer. + + + Migrate from another device + No comment provided by engineer. + + + Migrate here + No comment provided by engineer. + + + Migrate to another device + No comment provided by engineer. + + + Migrate to another device via QR code. + No comment provided by engineer. + + + Migrating + No comment provided by engineer. + Migrating database archive… กำลังย้ายข้อมูลที่เก็บถาวรของฐานข้อมูล… No comment provided by engineer. + + Migration complete + No comment provided by engineer. + Migration error: ข้อผิดพลาดในการย้ายข้อมูล: @@ -3674,6 +3858,10 @@ This is your link for group %@! Open group No comment provided by engineer. + + Open migration to another device + authentication reason + Open user profiles เปิดโปรไฟล์ผู้ใช้ @@ -3688,10 +3876,18 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or paste archive link + No comment provided by engineer. + Or scan QR code No comment provided by engineer. + + Or securely share this file link + No comment provided by engineer. + Or show this code No comment provided by engineer. @@ -3772,6 +3968,10 @@ This is your link for group %@! ข้อผิดพลาดในการถอดรหัสอย่างถาวร message decrypt error item + + Picture-in-picture calls + No comment provided by engineer. + Please ask your contact to enable sending voice messages. โปรดขอให้ผู้ติดต่อของคุณเปิดใช้งานการส่งข้อความเสียง @@ -3792,6 +3992,10 @@ This is your link for group %@! โปรดตรวจสอบความต้องการของคุณและการตั้งค่าผู้ติดต่อ No comment provided by engineer. + + Please confirm that network settings are correct for this device. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3847,6 +4051,10 @@ Error: %@ อาจเป็นไปได้ว่าลายนิ้วมือของ certificate ในที่อยู่เซิร์ฟเวอร์ไม่ถูกต้อง server test error + + Post-quantum E2EE + No comment provided by engineer. + Preserve the last message draft, with attachments. เก็บข้อความที่ร่างไว้ล่าสุดพร้อมไฟล์แนบ @@ -3979,6 +4187,14 @@ Error: %@ การแจ้งเตือนแบบทันที No comment provided by engineer. + + Push server + No comment provided by engineer. + + + Quantum resistant encryption + No comment provided by engineer. + Rate the app ให้คะแนนแอป @@ -4159,10 +4375,22 @@ Error: %@ Repeat connection request? No comment provided by engineer. + + Repeat download + No comment provided by engineer. + + + Repeat import + No comment provided by engineer. + Repeat join request? No comment provided by engineer. + + Repeat upload + No comment provided by engineer. + Reply ตอบ @@ -4262,6 +4490,10 @@ Error: %@ เซิร์ฟเวอร์ SMP No comment provided by engineer. + + Safer groups + No comment provided by engineer. + Save บันทึก @@ -4618,6 +4850,10 @@ Error: %@ ตั้งรหัสผ่าน No comment provided by engineer. + + Set passphrase + No comment provided by engineer. + Set passphrase to export ตั้งรหัสผ่านเพื่อส่งออก @@ -4672,6 +4908,10 @@ Error: %@ แชร์กับผู้ติดต่อ No comment provided by engineer. + + Show QR code + No comment provided by engineer. + Show calls in phone history แสดงการโทรในประวัติการโทร @@ -4808,6 +5048,10 @@ Error: %@ หยุด SimpleX authentication reason + + Stop chat + No comment provided by engineer. + Stop chat to enable database actions หยุดการแชทเพื่อเปิดใช้งานการดำเนินการกับฐานข้อมูล @@ -4848,6 +5092,10 @@ Error: %@ หยุดแชร์ที่อยู่ไหม? No comment provided by engineer. + + Stopping chat + No comment provided by engineer. + Submit ส่ง @@ -5090,6 +5338,14 @@ It can happen because of some bug or when the connection is compromised.การดำเนินการนี้ไม่สามารถยกเลิกได้ - โปรไฟล์ ผู้ติดต่อ ข้อความ และไฟล์ของคุณจะสูญหายไปอย่างถาวร No comment provided by engineer. + + This chat is protected by end-to-end encryption. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + E2EE info chat item + This device name No comment provided by engineer. @@ -5368,11 +5624,19 @@ To connect, please ask your contact to create another connection link and check อัปเกรดและเปิดการแชท No comment provided by engineer. + + Upload failed + No comment provided by engineer. + Upload file อัปโหลดไฟล์ server test step + + Uploading archive + No comment provided by engineer. + Use .onion hosts ใช้โฮสต์ .onion @@ -5419,6 +5683,10 @@ To connect, please ask your contact to create another connection link and check ใช้เซิร์ฟเวอร์ No comment provided by engineer. + + Use the app while in the call. + No comment provided by engineer. + User profile โปรไฟล์ผู้ใช้ @@ -5451,6 +5719,14 @@ To connect, please ask your contact to create another connection link and check Verify connections No comment provided by engineer. + + Verify database passphrase + No comment provided by engineer. + + + Verify passphrase + No comment provided by engineer. + Verify security code ตรวจสอบรหัสความปลอดภัย @@ -5538,6 +5814,10 @@ To connect, please ask your contact to create another connection link and check กําลังรอวิดีโอ No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + No comment provided by engineer. + Warning: you may lose some data! คำเตือน: คุณอาจสูญเสียข้อมูลบางส่วน! @@ -5558,6 +5838,10 @@ To connect, please ask your contact to create another connection link and check ข้อความต้อนรับ No comment provided by engineer. + + Welcome message is too long + No comment provided by engineer. + What's new มีอะไรใหม่ @@ -5611,6 +5895,10 @@ To connect, please ask your contact to create another connection link and check คุณ No comment provided by engineer. + + You **must not** use the same database on two devices. + No comment provided by engineer. + You accepted connection คุณยอมรับการเชื่อมต่อ @@ -5690,6 +5978,10 @@ Repeat join request? คุณสามารถเปิดใช้งานได้ในภายหลังผ่านการตั้งค่าความเป็นส่วนตัวและความปลอดภัยของแอป No comment provided by engineer. + + You can give another try. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. คุณสามารถซ่อนหรือปิดเสียงโปรไฟล์ผู้ใช้ - ปัดไปทางขวา @@ -6071,7 +6363,7 @@ SimpleX servers cannot see your profile. blocked - No comment provided by engineer. + marked deleted chat item preview text blocked %@ @@ -6079,7 +6371,7 @@ SimpleX servers cannot see your profile. blocked by admin - blocked chat item + marked deleted chat item preview text bold @@ -6503,7 +6795,7 @@ SimpleX servers cannot see your profile. moderated by %@ กลั่นกรองโดย %@ - No comment provided by engineer. + marked deleted chat item preview text months @@ -6572,6 +6864,10 @@ SimpleX servers cannot see your profile. เพื่อนต่อเพื่อน No comment provided by engineer. + + quantum resistant e2e encryption + chat item text + received answer… ได้รับคำตอบ… @@ -6642,6 +6938,10 @@ SimpleX servers cannot see your profile. set new profile picture profile update event chat item + + standard end-to-end encryption + chat item text + starting… กำลังเริ่มต้น… diff --git a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff index 09bc39f141..51ef266312 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -107,6 +107,11 @@ %@ bağlandı No comment provided by engineer. + + %@ downloaded + %@ indirildi + No comment provided by engineer. + %@ is connected! %@ bağlandı! @@ -127,6 +132,11 @@ %@ sunucuları No comment provided by engineer. + + %@ uploaded + %@ yüklendi + No comment provided by engineer. + %@ wants to connect! %@ bağlanmak istiyor! @@ -214,21 +224,22 @@ %lld messages blocked - %lld mesajlar engellendi + %lld mesaj engellendi No comment provided by engineer. %lld messages blocked by admin + %lld mesaj yönetici tarafından engellendi No comment provided by engineer. %lld messages marked deleted - %lld mesajlar silinmiş olarak işaretlendi + %lld mesaj silinmiş olarak işaretlendi No comment provided by engineer. %lld messages moderated by %@ - %lld mesajları %@ tarafından yönetildi + %lld mesaj %@ tarafından yönetildi No comment provided by engineer. @@ -293,7 +304,7 @@ %u messages skipped. - %u mesaj atlandı + %u mesajlar atlandı. No comment provided by engineer. @@ -341,6 +352,11 @@ **En gizli**: SimpleX Chat bildirim sunucusunu kullanma, arkaplanda mesajları periyodik olarak kontrol edin (uygulamayı ne sıklıkta kullandığınıza bağlıdır). No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + **Lütfen dikkat**: Aynı veritabanını iki cihazda kullanmak, güvenlik koruması olarak bağlantılarınızdaki mesajların şifresinin çözülmesini engelleyecektir. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Lütfen aklınızda bulunsun**: eğer parolanızı kaybederseniz parolanızı değiştirme veya geri kurtarma ihtimaliniz YOKTUR. @@ -356,6 +372,11 @@ **Dikkat**: Anında iletilen bildirimlere Anahtar Zinciri'nde kaydedilmiş parola gereklidir. No comment provided by engineer. + + **Warning**: the archive will be removed. + **Uyarı**: arşiv silinecektir. + No comment provided by engineer. + **e2e encrypted** audio call **uçtan uca şifrelenmiş** sesli arama @@ -612,6 +633,11 @@ Adres değişikliği iptal edilecek. Eski alıcı adresi kullanılacaktır. No comment provided by engineer. + + Admins can block a member for all. + Yöneticiler bir üyeyi tamamen engelleyebilirler. + No comment provided by engineer. + Admins can create the links to join groups. Yöneticiler gruplara katılmak için bağlantılar oluşturabilir. @@ -619,7 +645,7 @@ Advanced network settings - GGelişmiş ağ ayarları + Gelişmiş ağ ayarları No comment provided by engineer. @@ -644,6 +670,7 @@ All messages will be deleted - this cannot be undone! + Tüm mesajlar silinecektir - bu geri alınamaz! No comment provided by engineer. @@ -666,6 +693,11 @@ Tüm kişileriniz bağlı kalacaktır. Profil güncellemesi kişilerinize gönderilecektir. No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + Tüm kişileriniz, sohbetleriniz ve dosyalarınız güvenli bir şekilde şifrelenecek ve parçalar halinde yapılandırılmış XFTP rölelerine yüklenecektir. + No comment provided by engineer. + Allow İzin ver @@ -683,6 +715,7 @@ Allow irreversible message deletion only if your contact allows it to you. (24 hours) + Konuştuğun kişi, kalıcı olarak silinebilen mesajlara izin veriyorsa sen de ver. (24 saat içinde) No comment provided by engineer. @@ -707,6 +740,7 @@ Allow to irreversibly delete sent messages. (24 hours) + Gönderilen mesajların kalıcı olarak silinmesine izin ver. (24 saat içinde) No comment provided by engineer. @@ -741,6 +775,7 @@ Allow your contacts to irreversibly delete sent messages. (24 hours) + Kişilerinin gönderilen mesajları kalıcı olarak silmesine izin ver. (24 saat içinde) No comment provided by engineer. @@ -770,7 +805,7 @@ Always use relay - Her zaman yönlendirici kulan + Her zaman yönlendirici kullan No comment provided by engineer. @@ -788,6 +823,11 @@ Uygulama sürümü: %@ No comment provided by engineer. + + App data migration + Uygulama verisi taşıma + No comment provided by engineer. + App encrypts new local files (except videos). Uygulama yerel dosyaları şifreler (videolar dışında). @@ -823,6 +863,21 @@ Görünüş No comment provided by engineer. + + Apply + Uygula + No comment provided by engineer. + + + Archive and upload + Arşivle ve yükle + No comment provided by engineer. + + + Archiving database + Veritabanı arşivleniyor + No comment provided by engineer. + Attach Ekle @@ -920,6 +975,7 @@ Block for all + Herkes için engelle No comment provided by engineer. @@ -934,6 +990,7 @@ Block member for all? + Üye herkes için engellensin mi? No comment provided by engineer. @@ -943,6 +1000,7 @@ Blocked by admin + Yönetici tarafından engellendi No comment provided by engineer. @@ -952,6 +1010,7 @@ Both you and your contact can irreversibly delete sent messages. (24 hours) + Konuştuğun kişi ve sen mesajları kalıcı olarak silebilirsiniz. (24 saat içinde) No comment provided by engineer. @@ -1009,6 +1068,11 @@ İptal et No comment provided by engineer. + + Cancel migration + Taşımayı iptal et + No comment provided by engineer. + Cannot access keychain to save database password Veritabanı şifresini kaydetmek için Anahtar Zinciri'ne erişilemiyor @@ -1110,6 +1174,11 @@ Sohbet durduruldu. Bu veritabanını zaten başka bir cihazda kullandıysanız, sohbete başlamadan önce onu geri aktarmalısınız. No comment provided by engineer. + + Chat migrated! + Sohbet taşındı! + No comment provided by engineer. + Chat preferences Sohbet tercihleri @@ -1130,6 +1199,11 @@ Çince ve İspanyolca arayüz No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + Yeni cihazda _Başka bir cihazdan taşı_ seçeneğini seçin ve QR kodunu tarayın. + No comment provided by engineer. + Choose file Dosya seç @@ -1157,6 +1231,7 @@ Clear private notes? + Gizli notlar temizlensin mi? No comment provided by engineer. @@ -1199,6 +1274,11 @@ Veritabanı geliştirmelerini onayla No comment provided by engineer. + + Confirm network settings + Ağ ayarlarını onaylayın + No comment provided by engineer. + Confirm new passphrase… Yeni parolayı onayla… @@ -1209,6 +1289,16 @@ Şifreyi onayla No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + Taşımak için veritabanı parolasını hatırladığınızı doğrulayın. + No comment provided by engineer. + + + Confirm upload + Yüklemeyi onayla + No comment provided by engineer. + Connect Bağlan @@ -1455,10 +1545,12 @@ Bu senin kendi tek kullanımlık bağlantın! Created at + Şurada oluşturuldu No comment provided by engineer. Created at: %@ + Şurada oluşturuldu: %@ copied message info @@ -1466,6 +1558,11 @@ Bu senin kendi tek kullanımlık bağlantın! %@ de oluşturuldu No comment provided by engineer. + + Creating archive link + Arşiv bağlantısı oluşturuluyor + No comment provided by engineer. + Creating link… Link oluşturuluyor… @@ -1611,7 +1708,7 @@ Bu senin kendi tek kullanımlık bağlantın! Delete %lld messages? - %lld mesajları silinsin mi? + %lld mesaj silinsin mi? No comment provided by engineer. @@ -1686,6 +1783,11 @@ Bu geri alınamaz! Veritabanını sil No comment provided by engineer. + + Delete database from this device + Veritabanını bu cihazdan sil + No comment provided by engineer. + Delete file Dosyayı sil @@ -1953,6 +2055,7 @@ Bu geri alınamaz! Do not send history to new members. + Yeni üyelere geçmişi gönderme. No comment provided by engineer. @@ -1975,11 +2078,26 @@ Bu geri alınamaz! Sürüm düşür ve sohbeti aç No comment provided by engineer. + + Download failed + Yükleme başarısız oldu + No comment provided by engineer. + Download file Dosya indir server test step + + Downloading archive + Arşiv indiriliyor + No comment provided by engineer. + + + Downloading link details + Bağlantı detayları indiriliyor + No comment provided by engineer. + Duplicate display name! Yinelenen görünen ad! @@ -2035,6 +2153,11 @@ Bu geri alınamaz! Herkes için etkinleştir No comment provided by engineer. + + Enable in direct chats (BETA)! + Doğrudan sohbetlerde etkinleştirin (BETA)! + No comment provided by engineer. + Enable instant notifications? Anlık bildirimler etkinleştirilsin mi? @@ -2150,6 +2273,11 @@ Bu geri alınamaz! Grup adı gir… No comment provided by engineer. + + Enter passphrase + Parolayı girin + No comment provided by engineer. + Enter passphrase… Parola gir… @@ -2210,6 +2338,10 @@ Bu geri alınamaz! Üye(ler) eklenirken hata oluştu No comment provided by engineer. + + Error allowing contact PQ encryption + No comment provided by engineer. + Error changing address Adres değiştirilirken hata oluştu @@ -2247,6 +2379,7 @@ Bu geri alınamaz! Error creating message + Mesaj oluşturulurken hata No comment provided by engineer. @@ -2299,6 +2432,11 @@ Bu geri alınamaz! Kullanıcı profili silinirken hata oluştu No comment provided by engineer. + + Error downloading the archive + Arşiv indirilirken hata oluştu + No comment provided by engineer. + Error enabling delivery receipts! Görüldü bilgisi etkinleştirilirken hata oluştu! @@ -2374,6 +2512,11 @@ Bu geri alınamaz! Parolayı Anahtar Zincirine kaydederken hata oluştu No comment provided by engineer. + + Error saving settings + Ayarlar kaydedilirken hata oluştu + when migrating + Error saving user password Kullanıcı şifresi kaydedilirken hata oluştu @@ -2444,6 +2587,16 @@ Bu geri alınamaz! Kullanıcı gizliliği güncellenirken hata oluştu No comment provided by engineer. + + Error uploading the archive + Arşiv yüklenirken hata oluştu + No comment provided by engineer. + + + Error verifying passphrase: + Parola doğrulanırken hata oluştu: + No comment provided by engineer. + Error: Hata: @@ -2494,6 +2647,11 @@ Bu geri alınamaz! Dışarı çıkarılmış veritabanı arşivi. No comment provided by engineer. + + Exported file doesn't exist + Dışa aktarılan dosya mevcut değil + No comment provided by engineer. + Exporting database archive… Dışarı çıkarılmış veritabanı arşivi… @@ -2564,6 +2722,16 @@ Bu geri alınamaz! Favori ve okunmamış sohbetleri filtrele. No comment provided by engineer. + + Finalize migration + Taşıma işlemini sonlandır + No comment provided by engineer. + + + Finalize migration on another device. + Taşıma işlemini başka bir cihazda sonlandırın. + No comment provided by engineer. + Finally, we have them! 🚀 Sonunda, onlara sahibiz! 🚀 @@ -2716,6 +2884,7 @@ Bu geri alınamaz! Group members can irreversibly delete sent messages. (24 hours) + Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde) No comment provided by engineer. @@ -2825,6 +2994,7 @@ Bu geri alınamaz! History is not sent to new members. + Yeni üyelere geçmiş gönderilmedi. No comment provided by engineer. @@ -2852,6 +3022,10 @@ Bu geri alınamaz! Sunucularını nasıl kullanabilirsin No comment provided by engineer. + + Hungarian interface + No comment provided by engineer. + ICE servers (one per line) ICE sunucuları (her satıra bir tane) @@ -2917,8 +3091,19 @@ Bu geri alınamaz! Veritabanını içe aktar No comment provided by engineer. + + Import failed + İçe aktarma başarısız oldu + No comment provided by engineer. + + + Importing archive + Arşiv içe aktarılıyor + No comment provided by engineer. + Improved message delivery + İyileştirilmiş mesaj iletimi No comment provided by engineer. @@ -2931,6 +3116,11 @@ Bu geri alınamaz! Geliştirilmiş sunucu yapılandırması No comment provided by engineer. + + In order to continue, chat should be stopped. + Devam etmek için sohbetin durdurulması gerekiyor. + No comment provided by engineer. + In reply to Cevap olarak @@ -3035,6 +3225,7 @@ Bu geri alınamaz! Invalid display name! + Geçersiz görünen ad! No comment provided by engineer. @@ -3042,6 +3233,11 @@ Bu geri alınamaz! Geçersiz bağlantı No comment provided by engineer. + + Invalid migration confirmation + Geçersiz taşıma onayı + No comment provided by engineer. + Invalid name! Geçersiz isim! @@ -3145,6 +3341,7 @@ Bu geri alınamaz! Join group conversations + Grup sohbetlerine katıl No comment provided by engineer. @@ -3409,6 +3606,11 @@ Bu senin grup için bağlantın %@! Mesaj yazısı No comment provided by engineer. + + Message too large + Mesaj çok büyük + No comment provided by engineer. + Messages Mesajlar @@ -3424,11 +3626,47 @@ Bu senin grup için bağlantın %@! %@ den gelen mesajlar gösterilecektir! 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. + No comment provided by engineer. + + + Migrate device + No comment provided by engineer. + + + Migrate from another device + No comment provided by engineer. + + + Migrate here + No comment provided by engineer. + + + Migrate to another device + No comment provided by engineer. + + + Migrate to another device via QR code. + No comment provided by engineer. + + + Migrating + No comment provided by engineer. + Migrating database archive… Veritabanı arşivine geçiliyor… No comment provided by engineer. + + Migration complete + No comment provided by engineer. + Migration error: Geçiş hatası: @@ -3720,6 +3958,7 @@ Bu senin grup için bağlantın %@! Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours) + Mesajları yalnızca siz geri döndürülemez şekilde silebilirsiniz (kişiniz bunları silinmek üzere işaretleyebilir). (24 saat içinde) No comment provided by engineer. @@ -3744,6 +3983,7 @@ Bu senin grup için bağlantın %@! Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours) + Yalnızca kişiniz mesajları geri alınamaz şekilde silebilir (silinmeleri için işaretleyebilirsiniz). (24 saat içinde) No comment provided by engineer. @@ -3786,6 +4026,10 @@ Bu senin grup için bağlantın %@! Grubu aç No comment provided by engineer. + + Open migration to another device + authentication reason + Open user profiles Kullanıcı profillerini aç @@ -3801,11 +4045,19 @@ Bu senin grup için bağlantın %@! Uygulama açılıyor… No comment provided by engineer. + + Or paste archive link + No comment provided by engineer. + Or scan QR code Veya QR kodu okut No comment provided by engineer. + + Or securely share this file link + No comment provided by engineer. + Or show this code Veya bu kodu göster @@ -3853,6 +4105,7 @@ Bu senin grup için bağlantın %@! Past member %@ + Geçmiş üye %@ past/unknown group member @@ -3867,6 +4120,7 @@ Bu senin grup için bağlantın %@! Paste link to connect! + Bağlanmak için bağlantıyı yapıştır! No comment provided by engineer. @@ -3889,6 +4143,10 @@ Bu senin grup için bağlantın %@! Kalıcı şifre çözümü hatası message decrypt error item + + Picture-in-picture calls + No comment provided by engineer. + Please ask your contact to enable sending voice messages. Lütfen konuştuğunuz kişiden sesli mesaj göndermeyi etkinleştirmesini isteyin. @@ -3909,6 +4167,10 @@ Bu senin grup için bağlantın %@! Lütfen sizinkini ve iletişim tercihlerinizi kontrol edin. No comment provided by engineer. + + Please confirm that network settings are correct for this device. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3966,6 +4228,10 @@ Hata: %@ Muhtemelen, sunucu adresindeki parmakizi sertifikası doğru değil server test error + + Post-quantum E2EE + No comment provided by engineer. + Preserve the last message draft, with attachments. Son mesaj taslağını ekleriyle birlikte koru. @@ -4003,6 +4269,7 @@ Hata: %@ Private notes + Gizli notlar name of notes to self @@ -4100,6 +4367,14 @@ Hata: %@ Anında bildirimler No comment provided by engineer. + + Push server + No comment provided by engineer. + + + Quantum resistant encryption + No comment provided by engineer. + Rate the app Uygulamayı değerlendir @@ -4187,6 +4462,7 @@ Hata: %@ Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). + Yakın geçmiş ve geliştirilmiş [dizin botu](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex. im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion). No comment provided by engineer. @@ -4284,11 +4560,23 @@ Hata: %@ Bağlantı isteği tekrarlansın mı? No comment provided by engineer. + + Repeat download + No comment provided by engineer. + + + Repeat import + No comment provided by engineer. + Repeat join request? Katılma isteği tekrarlansın mı? No comment provided by engineer. + + Repeat upload + No comment provided by engineer. + Reply Yanıtla @@ -4389,6 +4677,10 @@ Hata: %@ SMP sunucuları No comment provided by engineer. + + Safer groups + No comment provided by engineer. + Save Kaydet @@ -4476,6 +4768,7 @@ Hata: %@ Saved message + Kaydedilmiş mesaj message info title @@ -4510,6 +4803,7 @@ Hata: %@ Search bar accepts invitation links. + Arama çubuğu davet bağlantılarını kabul eder. No comment provided by engineer. @@ -4624,6 +4918,7 @@ Hata: %@ Send up to 100 last messages to new members. + Yeni üyelere 100 adete kadar son mesajları gönderin. No comment provided by engineer. @@ -4751,6 +5046,10 @@ Hata: %@ Şifre ayarla No comment provided by engineer. + + Set passphrase + No comment provided by engineer. + Set passphrase to export Dışa aktarmak için parola ayarla @@ -4806,6 +5105,10 @@ Hata: %@ Kişilerle paylaş No comment provided by engineer. + + Show QR code + No comment provided by engineer. + Show calls in phone history Telefon geçmişinde aramaları göster @@ -4946,6 +5249,10 @@ Hata: %@ SimpleX'i durdur authentication reason + + Stop chat + No comment provided by engineer. + Stop chat to enable database actions Veritabanı eylemlerini etkinleştirmek için sohbeti durdur @@ -4986,6 +5293,10 @@ Hata: %@ Adresi paylaşmak durdurulsun mu? No comment provided by engineer. + + Stopping chat + No comment provided by engineer. + Submit Gönder @@ -5233,6 +5544,14 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Bu işlem geri alınamaz - profiliniz, kişileriniz, mesajlarınız ve dosyalarınız geri döndürülemez şekilde kaybolacaktır. No comment provided by engineer. + + This chat is protected by end-to-end encryption. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + E2EE info chat item + This device name Bu cihazın ismi @@ -5240,6 +5559,7 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. This display name is invalid. Please choose another name. + Bu görünen ad geçersiz. Lütfen başka bir isim seçin. No comment provided by engineer. @@ -5346,6 +5666,7 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Turkish interface + Türkçe arayüz No comment provided by engineer. @@ -5370,6 +5691,7 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Unblock for all + Herkes için engeli kaldır No comment provided by engineer. @@ -5379,6 +5701,7 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Unblock member for all? + Üyenin engeli herkes için kaldırılsın mı? No comment provided by engineer. @@ -5480,6 +5803,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Up to 100 last messages are sent to new members. + Yeni üyelere 100e kadar en son mesajlar gönderildi. No comment provided by engineer. @@ -5522,11 +5846,19 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Yükselt ve sohbeti aç No comment provided by engineer. + + Upload failed + No comment provided by engineer. + Upload file Dosya yükle server test step + + Uploading archive + No comment provided by engineer. + Use .onion hosts .onion ana bilgisayarlarını kullan @@ -5577,6 +5909,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Sunucu kullan No comment provided by engineer. + + Use the app while in the call. + No comment provided by engineer. + User profile Kullanıcı profili @@ -5612,6 +5948,14 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Bağlantıları doğrula No comment provided by engineer. + + Verify database passphrase + No comment provided by engineer. + + + Verify passphrase + No comment provided by engineer. + Verify security code Güvenlik kodunu doğrula @@ -5654,6 +5998,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Visible history + Görünür geçmiş chat feature @@ -5701,6 +6046,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Video bekleniyor No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + No comment provided by engineer. + Warning: you may lose some data! Uyarı: Bazı verileri kaybedebilirsin! @@ -5721,6 +6070,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Karşılama mesajı No comment provided by engineer. + + Welcome message is too long + No comment provided by engineer. + What's new Neler yeni @@ -5743,6 +6096,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste With encrypted files and media. + Şifrelenmiş dosyalar ve medya ile birlikte. No comment provided by engineer. @@ -5752,6 +6106,7 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste With reduced battery usage. + Azaltılmış pil kullanımı ile birlikte. No comment provided by engineer. @@ -5774,6 +6129,10 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Sen No comment provided by engineer. + + You **must not** use the same database on two devices. + No comment provided by engineer. + You accepted connection Bağlantıyı onayladın @@ -5861,6 +6220,10 @@ Katılma isteği tekrarlansın mı? Daha sonra uygulamanın Gizlilik ve Güvenlik ayarlarından etkinleştirebilirsiniz. No comment provided by engineer. + + You can give another try. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. Bir kullanıcı profilini gizleyebilir veya sessize alabilirsiniz - sağa kaydırın. @@ -5980,7 +6343,7 @@ Bağlantı isteği tekrarlansın mı? You rejected group invitation - Grup davetini reddettiniz. + Grup davetini reddettiniz No comment provided by engineer. @@ -6255,15 +6618,17 @@ SimpleX sunucuları profilinizi göremez. blocked engellendi - No comment provided by engineer. + marked deleted chat item preview text blocked %@ + engellendi %@ rcv group event chat item blocked by admin - blocked chat item + yönetici tarafından engellendi + marked deleted chat item preview text bold @@ -6387,6 +6752,7 @@ SimpleX sunucuları profilinizi göremez. contact %1$@ changed to %2$@ + %1$@ kişisi %2$@ olarak değişti profile update event chat item @@ -6546,7 +6912,7 @@ SimpleX sunucuları profilinizi göremez. event happened - etkinliği yaşandı + etkinlik yaşandı No comment provided by engineer. @@ -6621,7 +6987,7 @@ SimpleX sunucuları profilinizi göremez. invited %@ - %@ a davet edildi + %@ davet edildi rcv group event chat item @@ -6661,6 +7027,7 @@ SimpleX sunucuları profilinizi göremez. member %1$@ changed to %2$@ + kişi %1$@ , %2$@ olarak değişti profile update event chat item @@ -6691,7 +7058,7 @@ SimpleX sunucuları profilinizi göremez. moderated by %@ %@ tarafından yönetilmekte - No comment provided by engineer. + marked deleted chat item preview text months @@ -6760,6 +7127,10 @@ SimpleX sunucuları profilinizi göremez. eşler arası No comment provided by engineer. + + quantum resistant e2e encryption + chat item text + received answer… alınan cevap… @@ -6787,10 +7158,12 @@ SimpleX sunucuları profilinizi göremez. removed contact address + kişi adresi silindi profile update event chat item removed profile picture + profil fotoğrafı silindi profile update event chat item @@ -6825,12 +7198,18 @@ SimpleX sunucuları profilinizi göremez. set new contact address + yeni kişi adresi ayarla profile update event chat item set new profile picture + yeni profil fotoğrafı ayarla profile update event chat item + + standard end-to-end encryption + chat item text + starting… başlatılıyor… @@ -6838,7 +7217,7 @@ SimpleX sunucuları profilinizi göremez. strike - grev + çizik No comment provided by engineer. @@ -6848,6 +7227,7 @@ SimpleX sunucuları profilinizi göremez. unblocked %@ + engeli kaldırıldı %@ rcv group event chat item @@ -6857,6 +7237,7 @@ SimpleX sunucuları profilinizi göremez. unknown status + bilinmeyen durum No comment provided by engineer. @@ -6866,6 +7247,7 @@ SimpleX sunucuları profilinizi göremez. updated profile + güncellenmiş profil profile update event chat item @@ -6940,6 +7322,7 @@ SimpleX sunucuları profilinizi göremez. you blocked %@ + engelledin %@ snd group event chat item @@ -6984,6 +7367,7 @@ SimpleX sunucuları profilinizi göremez. you unblocked %@ + engelini kaldırdın %@ snd group event chat item @@ -6993,7 +7377,7 @@ SimpleX sunucuları profilinizi göremez. \~strike~ - \~strike~ + \~çizik~ No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff index 566f97e546..19b46bfc45 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -107,6 +107,10 @@ %@ підключено No comment provided by engineer. + + %@ downloaded + No comment provided by engineer. + %@ is connected! %@ підключено! @@ -127,6 +131,10 @@ %@ сервери No comment provided by engineer. + + %@ uploaded + No comment provided by engineer. + %@ wants to connect! %@ хоче підключитися! @@ -219,6 +227,7 @@ %lld messages blocked by admin + %lld повідомлень заблоковано адміністратором No comment provided by engineer. @@ -318,6 +327,7 @@ **Add contact**: to create a new invitation link, or connect via a link you received. + **Додати контакт**: створити нове посилання-запрошення або підключитися за отриманим посиланням. No comment provided by engineer. @@ -327,6 +337,7 @@ **Create group**: to create a new group. + **Створити групу**: створити нову групу. No comment provided by engineer. @@ -339,6 +350,10 @@ **Найбільш приватний**: не використовуйте сервер сповіщень SimpleX Chat, періодично перевіряйте повідомлення у фоновому режимі (залежить від того, як часто ви користуєтесь додатком). No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **Зверніть увагу: ви НЕ зможете відновити або змінити пароль, якщо втратите його. @@ -354,6 +369,10 @@ **Попередження**: Для отримання миттєвих пуш-сповіщень потрібна парольна фраза, збережена у брелоку. No comment provided by engineer. + + **Warning**: the archive will be removed. + No comment provided by engineer. + **e2e encrypted** audio call **e2e encrypted** аудіодзвінок @@ -567,6 +586,7 @@ Add contact + Додати контакт No comment provided by engineer. @@ -609,6 +629,10 @@ Зміна адреси буде скасована. Буде використано стару адресу отримання. No comment provided by engineer. + + Admins can block a member for all. + No comment provided by engineer. + Admins can create the links to join groups. Адміни можуть створювати посилання для приєднання до груп. @@ -641,6 +665,7 @@ All messages will be deleted - this cannot be undone! + Усі повідомлення будуть видалені - цю дію не можна скасувати! No comment provided by engineer. @@ -663,6 +688,10 @@ Всі ваші контакти залишаться на зв'язку. Повідомлення про оновлення профілю буде надіслано вашим контактам. No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + No comment provided by engineer. + Allow Дозволити @@ -788,6 +817,10 @@ Збірка програми: %@ No comment provided by engineer. + + App data migration + No comment provided by engineer. + App encrypts new local files (except videos). Додаток шифрує нові локальні файли (крім відео). @@ -823,6 +856,18 @@ Зовнішній вигляд No comment provided by engineer. + + Apply + No comment provided by engineer. + + + Archive and upload + No comment provided by engineer. + + + Archiving database + No comment provided by engineer. + Attach Прикріпити @@ -920,6 +965,7 @@ Block for all + Заблокувати для всіх No comment provided by engineer. @@ -934,6 +980,7 @@ Block member for all? + Заблокувати учасника для всіх? No comment provided by engineer. @@ -943,6 +990,7 @@ Blocked by admin + Заблокований адміністратором No comment provided by engineer. @@ -992,6 +1040,7 @@ Camera not available + Камера недоступна No comment provided by engineer. @@ -1009,6 +1058,10 @@ Скасувати No comment provided by engineer. + + Cancel migration + No comment provided by engineer. + Cannot access keychain to save database password Не вдається отримати доступ до зв'язки ключів для збереження пароля до бази даних @@ -1107,6 +1160,11 @@ Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + Чат зупинено. Якщо ви вже використовували цю базу даних на іншому пристрої, перенесіть її назад перед запуском чату. + No comment provided by engineer. + + + Chat migrated! No comment provided by engineer. @@ -1129,6 +1187,10 @@ Інтерфейс китайською та іспанською мовами No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + No comment provided by engineer. + Choose file Виберіть файл @@ -1198,6 +1260,10 @@ Підтвердити оновлення бази даних No comment provided by engineer. + + Confirm network settings + No comment provided by engineer. + Confirm new passphrase… Підтвердіть нову парольну фразу… @@ -1208,6 +1274,14 @@ Підтвердити пароль No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + No comment provided by engineer. + + + Confirm upload + No comment provided by engineer. + Connect Підключіться @@ -1457,6 +1531,10 @@ This is your own one-time link! Створено %@ No comment provided by engineer. + + Creating archive link + No comment provided by engineer. + Creating link… No comment provided by engineer. @@ -1672,6 +1750,10 @@ This cannot be undone! Видалити базу даних No comment provided by engineer. + + Delete database from this device + No comment provided by engineer. + Delete file Видалити файл @@ -1955,11 +2037,23 @@ This cannot be undone! Пониження та відкритий чат No comment provided by engineer. + + Download failed + No comment provided by engineer. + Download file Завантажити файл server test step + + Downloading archive + No comment provided by engineer. + + + Downloading link details + No comment provided by engineer. + Duplicate display name! Дублююче ім'я користувача! @@ -2014,6 +2108,10 @@ This cannot be undone! Увімкнути для всіх No comment provided by engineer. + + Enable in direct chats (BETA)! + No comment provided by engineer. + Enable instant notifications? Увімкнути миттєві сповіщення? @@ -2123,6 +2221,10 @@ This cannot be undone! Enter group name… No comment provided by engineer. + + Enter passphrase + No comment provided by engineer. + Enter passphrase… Введіть пароль… @@ -2181,6 +2283,10 @@ This cannot be undone! Помилка додавання користувача(ів) No comment provided by engineer. + + Error allowing contact PQ encryption + No comment provided by engineer. + Error changing address Помилка зміни адреси @@ -2268,6 +2374,10 @@ This cannot be undone! Помилка видалення профілю користувача No comment provided by engineer. + + Error downloading the archive + No comment provided by engineer. + Error enabling delivery receipts! Помилка активації підтвердження доставлення! @@ -2342,6 +2452,10 @@ This cannot be undone! Помилка збереження пароля на keychain No comment provided by engineer. + + Error saving settings + when migrating + Error saving user password Помилка збереження пароля користувача @@ -2410,6 +2524,14 @@ This cannot be undone! Помилка оновлення конфіденційності користувача No comment provided by engineer. + + Error uploading the archive + No comment provided by engineer. + + + Error verifying passphrase: + No comment provided by engineer. + Error: Помилка: @@ -2459,6 +2581,10 @@ This cannot be undone! Експортований архів бази даних. No comment provided by engineer. + + Exported file doesn't exist + No comment provided by engineer. + Exporting database archive… Експорт архіву бази даних… @@ -2528,6 +2654,14 @@ This cannot be undone! Фільтруйте непрочитані та улюблені чати. No comment provided by engineer. + + Finalize migration + No comment provided by engineer. + + + Finalize migration on another device. + No comment provided by engineer. + Finally, we have them! 🚀 Нарешті, вони у нас є! 🚀 @@ -2813,6 +2947,10 @@ This cannot be undone! Як користуватися вашими серверами No comment provided by engineer. + + Hungarian interface + No comment provided by engineer. + ICE servers (one per line) Сервери ICE (по одному на лінію) @@ -2878,6 +3016,14 @@ This cannot be undone! Імпорт бази даних No comment provided by engineer. + + Import failed + No comment provided by engineer. + + + Importing archive + No comment provided by engineer. + Improved message delivery No comment provided by engineer. @@ -2892,6 +3038,10 @@ This cannot be undone! Покращена конфігурація сервера No comment provided by engineer. + + In order to continue, chat should be stopped. + No comment provided by engineer. + In reply to У відповідь на @@ -2999,6 +3149,10 @@ This cannot be undone! Invalid link No comment provided by engineer. + + Invalid migration confirmation + No comment provided by engineer. + Invalid name! No comment provided by engineer. @@ -3354,6 +3508,10 @@ This is your link for group %@! Текст повідомлення No comment provided by engineer. + + Message too large + No comment provided by engineer. + Messages Повідомлення @@ -3368,11 +3526,47 @@ This is your link for group %@! Messages from %@ will be shown! 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. + No comment provided by engineer. + + + Migrate device + No comment provided by engineer. + + + Migrate from another device + No comment provided by engineer. + + + Migrate here + No comment provided by engineer. + + + Migrate to another device + No comment provided by engineer. + + + Migrate to another device via QR code. + No comment provided by engineer. + + + Migrating + No comment provided by engineer. + Migrating database archive… Перенесення архіву бази даних… No comment provided by engineer. + + Migration complete + No comment provided by engineer. + Migration error: Помилка міграції: @@ -3726,6 +3920,10 @@ This is your link for group %@! Open group No comment provided by engineer. + + Open migration to another device + authentication reason + Open user profiles Відкрити профілі користувачів @@ -3740,10 +3938,18 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or paste archive link + No comment provided by engineer. + Or scan QR code No comment provided by engineer. + + Or securely share this file link + No comment provided by engineer. + Or show this code No comment provided by engineer. @@ -3824,6 +4030,10 @@ This is your link for group %@! Постійна помилка розшифрування message decrypt error item + + Picture-in-picture calls + No comment provided by engineer. + Please ask your contact to enable sending voice messages. Будь ласка, попросіть вашого контакту увімкнути відправку голосових повідомлень. @@ -3844,6 +4054,10 @@ This is your link for group %@! Будь ласка, перевірте свої та контактні налаштування. No comment provided by engineer. + + Please confirm that network settings are correct for this device. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3899,6 +4113,10 @@ Error: %@ Можливо, в адресі сервера неправильно вказано відбиток сертифіката server test error + + Post-quantum E2EE + No comment provided by engineer. + Preserve the last message draft, with attachments. Зберегти чернетку останнього повідомлення з вкладеннями. @@ -4031,6 +4249,14 @@ Error: %@ Push-повідомлення No comment provided by engineer. + + Push server + No comment provided by engineer. + + + Quantum resistant encryption + No comment provided by engineer. + Rate the app Оцініть додаток @@ -4213,10 +4439,22 @@ Error: %@ Repeat connection request? No comment provided by engineer. + + Repeat download + No comment provided by engineer. + + + Repeat import + No comment provided by engineer. + Repeat join request? No comment provided by engineer. + + Repeat upload + No comment provided by engineer. + Reply Відповісти @@ -4316,6 +4554,10 @@ Error: %@ Сервери SMP No comment provided by engineer. + + Safer groups + No comment provided by engineer. + Save Зберегти @@ -4674,6 +4916,10 @@ Error: %@ Встановити пароль No comment provided by engineer. + + Set passphrase + No comment provided by engineer. + Set passphrase to export Встановити ключову фразу для експорту @@ -4728,6 +4974,10 @@ Error: %@ Поділіться з контактами No comment provided by engineer. + + Show QR code + No comment provided by engineer. + Show calls in phone history Показувати дзвінки в історії дзвінків @@ -4866,6 +5116,10 @@ Error: %@ Зупинити SimpleX authentication reason + + Stop chat + No comment provided by engineer. + Stop chat to enable database actions Зупиніть чат, щоб увімкнути дії з базою даних @@ -4906,6 +5160,10 @@ Error: %@ Припинити ділитися адресою? No comment provided by engineer. + + Stopping chat + No comment provided by engineer. + Submit Надіслати @@ -5148,6 +5406,14 @@ It can happen because of some bug or when the connection is compromised.Цю дію неможливо скасувати - ваш профіль, контакти, повідомлення та файли будуть безповоротно втрачені. No comment provided by engineer. + + This chat is protected by end-to-end encryption. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + E2EE info chat item + This device name No comment provided by engineer. @@ -5427,11 +5693,19 @@ To connect, please ask your contact to create another connection link and check Оновлення та відкритий чат No comment provided by engineer. + + Upload failed + No comment provided by engineer. + Upload file Завантажити файл server test step + + Uploading archive + No comment provided by engineer. + Use .onion hosts Використовуйте хости .onion @@ -5480,6 +5754,10 @@ To connect, please ask your contact to create another connection link and check Використовувати сервер No comment provided by engineer. + + Use the app while in the call. + No comment provided by engineer. + User profile Профіль користувача @@ -5512,6 +5790,14 @@ To connect, please ask your contact to create another connection link and check Verify connections No comment provided by engineer. + + Verify database passphrase + No comment provided by engineer. + + + Verify passphrase + No comment provided by engineer. + Verify security code Підтвердіть код безпеки @@ -5599,6 +5885,10 @@ To connect, please ask your contact to create another connection link and check Чекаємо на відео No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + No comment provided by engineer. + Warning: you may lose some data! Попередження: ви можете втратити деякі дані! @@ -5619,6 +5909,10 @@ To connect, please ask your contact to create another connection link and check Вітальне повідомлення No comment provided by engineer. + + Welcome message is too long + No comment provided by engineer. + What's new Що нового @@ -5672,6 +5966,10 @@ To connect, please ask your contact to create another connection link and check Ти No comment provided by engineer. + + You **must not** use the same database on two devices. + No comment provided by engineer. + You accepted connection Ви прийняли підключення @@ -5751,6 +6049,10 @@ Repeat join request? Ви можете увімкнути їх пізніше в налаштуваннях конфіденційності та безпеки програми. No comment provided by engineer. + + You can give another try. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. Ви можете приховати або вимкнути звук профілю користувача - проведіть по ньому вправо. @@ -6134,7 +6436,7 @@ SimpleX servers cannot see your profile. blocked - No comment provided by engineer. + marked deleted chat item preview text blocked %@ @@ -6142,7 +6444,7 @@ SimpleX servers cannot see your profile. blocked by admin - blocked chat item + marked deleted chat item preview text bold @@ -6568,7 +6870,7 @@ SimpleX servers cannot see your profile. moderated by %@ модерується %@ - No comment provided by engineer. + marked deleted chat item preview text months @@ -6637,6 +6939,10 @@ SimpleX servers cannot see your profile. одноранговий No comment provided by engineer. + + quantum resistant e2e encryption + chat item text + received answer… отримали відповідь… @@ -6707,6 +7013,10 @@ SimpleX servers cannot see your profile. set new profile picture profile update event chat item + + standard end-to-end encryption + chat item text + starting… починаючи… diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 6209cfb41f..3bf27bff9c 100644 --- a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff +++ b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff @@ -89,6 +89,7 @@ %@ and %@ + %@ 和 %@ No comment provided by engineer. @@ -103,6 +104,11 @@ %@ connected + %@ 已连接 + No comment provided by engineer. + + + %@ downloaded No comment provided by engineer. @@ -125,6 +131,10 @@ %@ 服务器 No comment provided by engineer. + + %@ uploaded + No comment provided by engineer. + %@ wants to connect! %@ 要连接! @@ -132,6 +142,7 @@ %@, %@ and %lld members + %@, %@ 和 %lld 成员 No comment provided by engineer. @@ -201,6 +212,7 @@ %lld group events + %lld 群组事件 No comment provided by engineer. @@ -210,6 +222,7 @@ %lld messages blocked + %lld 条消息已屏蔽 No comment provided by engineer. @@ -330,6 +343,10 @@ **最私密**:不使用 SimpleX Chat 通知服务器,在后台定期检查消息(取决于您多经常使用应用程序)。 No comment provided by engineer. + + **Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection. + No comment provided by engineer. + **Please note**: you will NOT be able to recover or change passphrase if you lose it. **请注意**:如果您丢失密码,您将无法恢复或者更改密码。 @@ -345,6 +362,10 @@ **警告**:及时推送通知需要保存在钥匙串的密码。 No comment provided by engineer. + + **Warning**: the archive will be removed. + No comment provided by engineer. + **e2e encrypted** audio call **端到端加密** 语音通话 @@ -387,6 +408,9 @@ - optionally notify deleted contacts. - profile names with spaces. - and more! + - 可选择通知已删除的联系人。 +- 带空格的个人资料名称。 +- 以及更多! No comment provided by engineer. @@ -554,6 +578,7 @@ Add contact + 添加联系人 No comment provided by engineer. @@ -596,6 +621,10 @@ 将中止地址更改。将使用旧接收地址。 No comment provided by engineer. + + Admins can block a member for all. + No comment provided by engineer. + Admins can create the links to join groups. 管理员可以创建链接以加入群组。 @@ -628,6 +657,7 @@ All messages will be deleted - this cannot be undone! + 所有消息都将被删除 - 这无法被撤销! No comment provided by engineer. @@ -649,6 +679,10 @@ 您的所有联系人将保持连接。个人资料更新将发送给您的联系人。 No comment provided by engineer. + + All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + No comment provided by engineer. + Allow 允许 @@ -746,10 +780,12 @@ Already connecting! + 已经在连接了! No comment provided by engineer. Already joining the group! + 已经加入了该群组! No comment provided by engineer. @@ -772,6 +808,10 @@ 应用程序构建:%@ No comment provided by engineer. + + App data migration + No comment provided by engineer. + App encrypts new local files (except videos). 应用程序为新的本地文件(视频除外)加密。 @@ -807,6 +847,18 @@ 外观 No comment provided by engineer. + + Apply + No comment provided by engineer. + + + Archive and upload + No comment provided by engineer. + + + Archiving database + No comment provided by engineer. + Attach 附件 @@ -874,6 +926,7 @@ Bad desktop address + 糟糕的桌面地址 No comment provided by engineer. @@ -888,6 +941,7 @@ Better groups + 更佳的群组 No comment provided by engineer. @@ -897,30 +951,37 @@ Block + 封禁 No comment provided by engineer. Block for all + 为所有人封禁 No comment provided by engineer. Block group members + 屏蔽群组成员 No comment provided by engineer. Block member + 封禁成员 No comment provided by engineer. Block member for all? + 为所有其他成员封禁该成员? No comment provided by engineer. Block member? + 封禁成员吗? No comment provided by engineer. Blocked by admin + 由管理员封禁 No comment provided by engineer. @@ -970,6 +1031,7 @@ Camera not available + 相机不可用 No comment provided by engineer. @@ -987,6 +1049,10 @@ 取消 No comment provided by engineer. + + Cancel migration + No comment provided by engineer. + Cannot access keychain to save database password 无法访问钥匙串以保存数据库密码 @@ -1085,6 +1151,11 @@ Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat. + 聊天已停止。如果你已经在另一台设备商使用过此数据库,你应该在启动聊天前将数据库传输回来。 + No comment provided by engineer. + + + Chat migrated! No comment provided by engineer. @@ -1107,6 +1178,10 @@ 中文和西班牙文界面 No comment provided by engineer. + + Choose _Migrate from another device_ on the new device and scan QR code. + No comment provided by engineer. + Choose file 选择文件 @@ -1134,6 +1209,7 @@ Clear private notes? + 清除私密笔记? No comment provided by engineer. @@ -1176,6 +1252,10 @@ 确认数据库升级 No comment provided by engineer. + + Confirm network settings + No comment provided by engineer. + Confirm new passphrase… 确认新密码…… @@ -1186,6 +1266,14 @@ 确认密码 No comment provided by engineer. + + Confirm that you remember database passphrase to migrate it. + No comment provided by engineer. + + + Confirm upload + No comment provided by engineer. + Connect 连接 @@ -1193,6 +1281,7 @@ Connect automatically + 自动连接 No comment provided by engineer. @@ -1202,10 +1291,12 @@ Connect to desktop + 连接到桌面 No comment provided by engineer. Connect to yourself? + 连接到你自己? No comment provided by engineer. @@ -1238,10 +1329,12 @@ This is your own one-time link! Connected desktop + 已连接的桌面 No comment provided by engineer. Connected to desktop + 已连接到桌面 No comment provided by engineer. @@ -1256,6 +1349,7 @@ This is your own one-time link! Connecting to desktop + 正连接到桌面 No comment provided by engineer. @@ -1280,6 +1374,7 @@ This is your own one-time link! Connection terminated + 连接被终止 No comment provided by engineer. @@ -1363,6 +1458,7 @@ This is your own one-time link! Create a group using a random profile. + 使用随机身份创建群组 No comment provided by engineer. @@ -1377,6 +1473,7 @@ This is your own one-time link! Create group + 建群 No comment provided by engineer. @@ -1396,6 +1493,7 @@ This is your own one-time link! Create profile + 创建个人资料 No comment provided by engineer. @@ -1415,6 +1513,7 @@ This is your own one-time link! Created at + 创建于 No comment provided by engineer. @@ -1426,8 +1525,13 @@ This is your own one-time link! 创建于 %@ No comment provided by engineer. + + Creating archive link + No comment provided by engineer. + Creating link… + 创建链接中… No comment provided by engineer. @@ -1599,6 +1703,7 @@ This is your own one-time link! Delete and notify contact + 删除并通知联系人 No comment provided by engineer. @@ -1641,6 +1746,10 @@ This cannot be undone! 删除数据库 No comment provided by engineer. + + Delete database from this device + No comment provided by engineer. + Delete file 删除文件 @@ -1778,6 +1887,7 @@ This cannot be undone! Desktop address + 桌面地址 No comment provided by engineer. @@ -1786,6 +1896,7 @@ This cannot be undone! Desktop devices + 桌面设备 No comment provided by engineer. @@ -1880,6 +1991,7 @@ This cannot be undone! Disconnect desktop? + 断开桌面连接? No comment provided by engineer. @@ -1889,6 +2001,7 @@ This cannot be undone! Discover via local network + 通过本地网络发现 No comment provided by engineer. @@ -1903,6 +2016,7 @@ This cannot be undone! Do not send history to new members. + 不给新成员发送历史消息。 No comment provided by engineer. @@ -1925,11 +2039,23 @@ This cannot be undone! 降级并打开聊天 No comment provided by engineer. + + Download failed + No comment provided by engineer. + Download file 下载文件 server test step + + Downloading archive + No comment provided by engineer. + + + Downloading link details + No comment provided by engineer. + Duplicate display name! 重复的显示名! @@ -1977,6 +2103,7 @@ This cannot be undone! Enable camera access + 启用相机访问 No comment provided by engineer. @@ -1984,6 +2111,10 @@ This cannot be undone! 全部启用 No comment provided by engineer. + + Enable in direct chats (BETA)! + No comment provided by engineer. + Enable instant notifications? 启用即时通知? @@ -2075,10 +2206,12 @@ This cannot be undone! Encryption re-negotiation error + 加密重协商错误 message decrypt error item Encryption re-negotiation failed. + 加密重协商失败了。 No comment provided by engineer. @@ -2095,6 +2228,10 @@ This cannot be undone! Enter group name… No comment provided by engineer. + + Enter passphrase + No comment provided by engineer. + Enter passphrase… 输入密码…… @@ -2112,6 +2249,7 @@ This cannot be undone! Enter this device name… + 输入此设备名… No comment provided by engineer. @@ -2153,6 +2291,10 @@ This cannot be undone! 添加成员错误 No comment provided by engineer. + + Error allowing contact PQ encryption + No comment provided by engineer. + Error changing address 更改地址错误 @@ -2190,6 +2332,7 @@ This cannot be undone! Error creating message + 创建消息出错 No comment provided by engineer. @@ -2242,6 +2385,10 @@ This cannot be undone! 删除用户资料错误 No comment provided by engineer. + + Error downloading the archive + No comment provided by engineer. + Error enabling delivery receipts! 启用送达回执出错! @@ -2316,6 +2463,10 @@ This cannot be undone! 保存密码到钥匙串错误 No comment provided by engineer. + + Error saving settings + when migrating + Error saving user password 保存用户密码时出错 @@ -2385,6 +2536,14 @@ This cannot be undone! 更新用户隐私时出错 No comment provided by engineer. + + Error uploading the archive + No comment provided by engineer. + + + Error verifying passphrase: + No comment provided by engineer. + Error: 错误: @@ -2417,6 +2576,7 @@ This cannot be undone! Expand + 展开 chat item action @@ -2434,6 +2594,10 @@ This cannot be undone! 导出数据库归档。 No comment provided by engineer. + + Exported file doesn't exist + No comment provided by engineer. + Exporting database archive… 导出数据库档案中… @@ -2451,6 +2615,7 @@ This cannot be undone! Faster joining and more reliable messages. + 加入速度更快、信息更可靠。 No comment provided by engineer. @@ -2503,6 +2668,14 @@ This cannot be undone! 过滤未读和收藏的聊天记录。 No comment provided by engineer. + + Finalize migration + No comment provided by engineer. + + + Finalize migration on another device. + No comment provided by engineer. + Finally, we have them! 🚀 终于我们有它们了! 🚀 @@ -2550,6 +2723,7 @@ This cannot be undone! Found desktop + 找到了桌面 No comment provided by engineer. @@ -2574,6 +2748,7 @@ This cannot be undone! Fully decentralized – visible only to members. + 完全去中心化 - 仅对成员可见。 No comment provided by engineer. @@ -2602,6 +2777,7 @@ This cannot be undone! Group already exists! + 群已存在! No comment provided by engineer. @@ -2761,6 +2937,7 @@ This cannot be undone! History is not sent to new members. + 未发送历史消息给新成员。 No comment provided by engineer. @@ -2788,6 +2965,10 @@ This cannot be undone! 如何使用您的服务器 No comment provided by engineer. + + Hungarian interface + No comment provided by engineer. + ICE servers (one per line) ICE 服务器(每行一个) @@ -2853,8 +3034,17 @@ This cannot be undone! 导入数据库 No comment provided by engineer. + + Import failed + No comment provided by engineer. + + + Importing archive + No comment provided by engineer. + Improved message delivery + 改进了消息传递 No comment provided by engineer. @@ -2867,6 +3057,10 @@ This cannot be undone! 改进的服务器配置 No comment provided by engineer. + + In order to continue, chat should be stopped. + No comment provided by engineer. + In reply to 答复 @@ -2879,6 +3073,7 @@ This cannot be undone! Incognito groups + 匿名群组 No comment provided by engineer. @@ -2913,6 +3108,7 @@ This cannot be undone! Incompatible version + 不兼容的版本 No comment provided by engineer. @@ -2959,6 +3155,7 @@ This cannot be undone! Invalid QR code + 无效的二维码 No comment provided by engineer. @@ -2968,14 +3165,20 @@ This cannot be undone! Invalid display name! + 无效的显示名! No comment provided by engineer. Invalid link No comment provided by engineer. + + Invalid migration confirmation + No comment provided by engineer. + Invalid name! + 无效名称! No comment provided by engineer. @@ -3075,10 +3278,12 @@ This cannot be undone! Join group conversations + 加入群对话 No comment provided by engineer. Join group? + 加入群组? No comment provided by engineer. @@ -3102,6 +3307,7 @@ This is your link for group %@! Keep + 保留 No comment provided by engineer. @@ -3110,6 +3316,7 @@ This is your link for group %@! Keep unused invitation? + 保留未使用的邀请吗? No comment provided by engineer. @@ -3174,14 +3381,17 @@ This is your link for group %@! Link mobile and desktop apps! 🔗 + 连接移动端和桌面端应用程序!🔗 No comment provided by engineer. Linked desktop options + 已链接桌面选项 No comment provided by engineer. Linked desktops + 已链接桌面 No comment provided by engineer. @@ -3329,6 +3539,10 @@ This is your link for group %@! 消息正文 No comment provided by engineer. + + Message too large + No comment provided by engineer. + Messages 消息 @@ -3343,11 +3557,47 @@ This is your link for group %@! Messages from %@ will be shown! 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. + No comment provided by engineer. + + + Migrate device + No comment provided by engineer. + + + Migrate from another device + No comment provided by engineer. + + + Migrate here + No comment provided by engineer. + + + Migrate to another device + No comment provided by engineer. + + + Migrate to another device via QR code. + No comment provided by engineer. + + + Migrating + No comment provided by engineer. + Migrating database archive… 迁移数据库档案中… No comment provided by engineer. + + Migration complete + No comment provided by engineer. + Migration error: 迁移错误: @@ -3440,6 +3690,7 @@ This is your link for group %@! New chat + 新聊天 No comment provided by engineer. @@ -3544,6 +3795,7 @@ This is your link for group %@! Not compatible! + 不兼容! No comment provided by engineer. @@ -3567,6 +3819,7 @@ This is your link for group %@! OK + 好的 No comment provided by engineer. @@ -3701,8 +3954,13 @@ This is your link for group %@! Open group + 打开群 No comment provided by engineer. + + Open migration to another device + authentication reason + Open user profiles 打开用户个人资料 @@ -3717,12 +3975,22 @@ This is your link for group %@! Opening app… No comment provided by engineer. + + Or paste archive link + No comment provided by engineer. + Or scan QR code + 或者扫描二维码 + No comment provided by engineer. + + + Or securely share this file link No comment provided by engineer. Or show this code + 或者显示此码 No comment provided by engineer. @@ -3771,6 +4039,7 @@ This is your link for group %@! Paste desktop address + 粘贴桌面地址 No comment provided by engineer. @@ -3780,10 +4049,12 @@ This is your link for group %@! Paste link to connect! + 粘贴链接以连接! No comment provided by engineer. Paste the link you received + 粘贴您收到的链接 No comment provided by engineer. @@ -3801,6 +4072,10 @@ This is your link for group %@! 解密错误 message decrypt error item + + Picture-in-picture calls + No comment provided by engineer. + Please ask your contact to enable sending voice messages. 请让您的联系人启用发送语音消息。 @@ -3821,6 +4096,10 @@ This is your link for group %@! 请检查您和您的联系人偏好设置。 No comment provided by engineer. + + Please confirm that network settings are correct for this device. + No comment provided by engineer. + Please contact developers. Error: %@ @@ -3876,6 +4155,10 @@ Error: %@ 服务器地址中的证书指纹可能不正确 server test error + + Post-quantum E2EE + No comment provided by engineer. + Preserve the last message draft, with attachments. 保留最后的消息草稿及其附件。 @@ -3913,6 +4196,7 @@ Error: %@ Private notes + 私密笔记 name of notes to self @@ -3931,6 +4215,7 @@ Error: %@ Profile name: + 显示名: No comment provided by engineer. @@ -4008,6 +4293,14 @@ Error: %@ 推送通知 No comment provided by engineer. + + Push server + No comment provided by engineer. + + + Quantum resistant encryption + No comment provided by engineer. + Rate the app 评价此应用程序 @@ -4035,6 +4328,7 @@ Error: %@ Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode). + 阅读更多[User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)。 No comment provided by engineer. @@ -4188,10 +4482,24 @@ Error: %@ Repeat connection request? + 重复连接请求吗? + No comment provided by engineer. + + + Repeat download + No comment provided by engineer. + + + Repeat import No comment provided by engineer. Repeat join request? + 重复加入请求吗? + No comment provided by engineer. + + + Repeat upload No comment provided by engineer. @@ -4251,6 +4559,7 @@ Error: %@ Retry + 重试 No comment provided by engineer. @@ -4293,6 +4602,10 @@ Error: %@ SMP 服务器 No comment provided by engineer. + + Safer groups + No comment provided by engineer. + Save 保存 @@ -4380,6 +4693,7 @@ Error: %@ Saved message + 已保存的消息 message info title @@ -4389,6 +4703,7 @@ Error: %@ Scan QR code from desktop + 从桌面扫描二维码 No comment provided by engineer. @@ -4413,10 +4728,12 @@ Error: %@ Search bar accepts invitation links. + 搜索栏接受邀请链接。 No comment provided by engineer. Search or paste SimpleX link + 搜索或粘贴 SimpleX 链接 No comment provided by engineer. @@ -4526,6 +4843,7 @@ Error: %@ Send up to 100 last messages to new members. + 给新成员发送最多 100 条历史消息。 No comment provided by engineer. @@ -4625,6 +4943,7 @@ Error: %@ Session code + 会话码 No comment provided by engineer. @@ -4652,6 +4971,10 @@ Error: %@ 设置密码 No comment provided by engineer. + + Set passphrase + No comment provided by engineer. + Set passphrase to export 设置密码来导出 @@ -4699,6 +5022,7 @@ Error: %@ Share this 1-time invite link + 分享此一次性邀请链接 No comment provided by engineer. @@ -4706,6 +5030,10 @@ Error: %@ 与联系人分享 No comment provided by engineer. + + Show QR code + No comment provided by engineer. + Show calls in phone history 在电话历史记录中显示通话 @@ -4828,6 +5156,7 @@ Error: %@ Start chat? + 启动聊天吗? No comment provided by engineer. @@ -4845,6 +5174,10 @@ Error: %@ 停止 SimpleX authentication reason + + Stop chat + No comment provided by engineer. + Stop chat to enable database actions 停止聊天以启用数据库操作 @@ -4885,6 +5218,10 @@ Error: %@ 停止分享地址? No comment provided by engineer. + + Stopping chat + No comment provided by engineer. + Submit 提交 @@ -4937,6 +5274,7 @@ Error: %@ Tap to Connect + 轻按连接 No comment provided by engineer. @@ -4956,10 +5294,12 @@ Error: %@ Tap to paste link + 轻按粘贴链接 No comment provided by engineer. Tap to scan + 轻按扫描 No comment provided by engineer. @@ -5026,6 +5366,7 @@ It can happen because of some bug or when the connection is compromised. The code you scanned is not a SimpleX link QR code. + 您扫描的码不是 SimpleX 链接的二维码。 No comment provided by engineer. @@ -5095,6 +5436,7 @@ It can happen because of some bug or when the connection is compromised. The text you pasted is not a SimpleX link. + 您粘贴的文本不是 SimpleX 链接。 No comment provided by engineer. @@ -5127,12 +5469,22 @@ It can happen because of some bug or when the connection is compromised.此操作无法撤消——您的个人资料、联系人、消息和文件将不可撤回地丢失。 No comment provided by engineer. + + This chat is protected by end-to-end encryption. + E2EE info chat item + + + This chat is protected by quantum resistant end-to-end encryption. + E2EE info chat item + This device name + 此设备名称 No comment provided by engineer. This display name is invalid. Please choose another name. + 显示名无效。请另选一个名称。 No comment provided by engineer. @@ -5147,10 +5499,12 @@ It can happen because of some bug or when the connection is compromised. This is your own SimpleX address! + 这是你自己的 SimpleX 地址! No comment provided by engineer. This is your own one-time link! + 这是你自己的一次性链接! No comment provided by engineer. @@ -5170,6 +5524,7 @@ It can happen because of some bug or when the connection is compromised. To hide unwanted messages. + 隐藏不需要的信息。 No comment provided by engineer. @@ -5255,22 +5610,27 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock + 解封 No comment provided by engineer. Unblock for all + 为所有人解封 No comment provided by engineer. Unblock member + 解封成员 No comment provided by engineer. Unblock member for all? + 为所有其他成员解封该成员? No comment provided by engineer. Unblock member? + 解封成员吗? No comment provided by engineer. @@ -5337,10 +5697,12 @@ To connect, please ask your contact to create another connection link and check Unlink + 取消链接 No comment provided by engineer. Unlink desktop? + 取消链接桌面端? No comment provided by engineer. @@ -5365,6 +5727,7 @@ To connect, please ask your contact to create another connection link and check Up to 100 last messages are sent to new members. + 给新成员发送了最多 100 条历史消息。 No comment provided by engineer. @@ -5407,11 +5770,19 @@ To connect, please ask your contact to create another connection link and check 升级并打开聊天 No comment provided by engineer. + + Upload failed + No comment provided by engineer. + Upload file 上传文件 server test step + + Uploading archive + No comment provided by engineer. + Use .onion hosts 使用 .onion 主机 @@ -5439,6 +5810,7 @@ To connect, please ask your contact to create another connection link and check Use from desktop + 从桌面端使用 No comment provided by engineer. @@ -5460,6 +5832,10 @@ To connect, please ask your contact to create another connection link and check 使用服务器 No comment provided by engineer. + + Use the app while in the call. + No comment provided by engineer. + User profile 用户资料 @@ -5477,10 +5853,12 @@ To connect, please ask your contact to create another connection link and check Verify code with desktop + 用桌面端验证代码 No comment provided by engineer. Verify connection + 验证连接 No comment provided by engineer. @@ -5490,6 +5868,15 @@ To connect, please ask your contact to create another connection link and check Verify connections + 验证连接 + No comment provided by engineer. + + + Verify database passphrase + No comment provided by engineer. + + + Verify passphrase No comment provided by engineer. @@ -5504,6 +5891,7 @@ To connect, please ask your contact to create another connection link and check Via secure quantum resistant protocol. + 通过安全的、抗量子计算机破解的协议。 No comment provided by engineer. @@ -5533,6 +5921,7 @@ To connect, please ask your contact to create another connection link and check Visible history + 可见的历史 chat feature @@ -5579,6 +5968,10 @@ To connect, please ask your contact to create another connection link and check 等待视频中 No comment provided by engineer. + + Warning: starting chat on multiple devices is not supported and will cause message delivery failures + No comment provided by engineer. + Warning: you may lose some data! 警告:您可能会丢失部分数据! @@ -5599,6 +5992,10 @@ To connect, please ask your contact to create another connection link and check 欢迎消息 No comment provided by engineer. + + Welcome message is too long + No comment provided by engineer. + What's new 更新内容 @@ -5621,6 +6018,7 @@ To connect, please ask your contact to create another connection link and check With encrypted files and media. + 加密的文件和媒体。 No comment provided by engineer. @@ -5630,6 +6028,7 @@ To connect, please ask your contact to create another connection link and check With reduced battery usage. + 降低了电量使用。 No comment provided by engineer. @@ -5652,6 +6051,10 @@ To connect, please ask your contact to create another connection link and check No comment provided by engineer. + + You **must not** use the same database on two devices. + No comment provided by engineer. + You accepted connection 您已接受连接 @@ -5678,6 +6081,7 @@ To connect, please ask your contact to create another connection link and check You are already connecting via this one-time link! + 你已经在通过这个一次性链接进行连接! No comment provided by engineer. @@ -5694,6 +6098,7 @@ To connect, please ask your contact to create another connection link and check You are already joining the group via this link. + 你已经在通过此链接加入该群。 No comment provided by engineer. @@ -5731,6 +6136,10 @@ Repeat join request? 您可以稍后通过应用程序的 "隐私与安全 "设置启用它们。 No comment provided by engineer. + + You can give another try. + No comment provided by engineer. + You can hide or mute a user profile - swipe it to the right. 您可以隐藏或静音用户个人资料——只需向右滑动。 @@ -5738,6 +6147,7 @@ Repeat join request? You can make it visible to your SimpleX contacts via Settings. + 你可以通过设置让它对你的 SimpleX 联系人可见。 No comment provided by engineer. @@ -5782,6 +6192,7 @@ Repeat join request? You can view invitation link again in connection details. + 您可以在连接详情中再次查看邀请链接。 No comment provided by engineer. @@ -5801,6 +6212,7 @@ Repeat join request? You have already requested connection via this address! + 你已经请求通过此地址进行连接! No comment provided by engineer. @@ -5879,6 +6291,7 @@ Repeat connection request? You will connect to all group members. + 你将连接到所有群成员。 No comment provided by engineer. @@ -6100,6 +6513,7 @@ SimpleX 服务器无法看到您的资料。 author + 作者 member role @@ -6114,15 +6528,18 @@ SimpleX 服务器无法看到您的资料。 blocked - No comment provided by engineer. + 已封禁 + marked deleted chat item preview text blocked %@ + 已封禁 %@ rcv group event chat item blocked by admin - blocked chat item + 由管理员封禁 + marked deleted chat item preview text bold @@ -6300,6 +6717,7 @@ SimpleX 服务器无法看到您的资料。 deleted contact + 已删除联系人 rcv direct event chat item @@ -6549,7 +6967,7 @@ SimpleX 服务器无法看到您的资料。 moderated by %@ 由 %@ 审核 - No comment provided by engineer. + marked deleted chat item preview text months @@ -6618,6 +7036,10 @@ SimpleX 服务器无法看到您的资料。 点对点 No comment provided by engineer. + + quantum resistant e2e encryption + chat item text + received answer… 已收到回复…… @@ -6645,10 +7067,12 @@ SimpleX 服务器无法看到您的资料。 removed contact address + 删除了联系地址 profile update event chat item removed profile picture + 删除了资料图片 profile update event chat item @@ -6683,12 +7107,18 @@ SimpleX 服务器无法看到您的资料。 set new contact address + 设置新的联系地址 profile update event chat item set new profile picture + 设置新的资料图片 profile update event chat item + + standard end-to-end encryption + chat item text + starting… 启动中…… @@ -6715,6 +7145,7 @@ SimpleX 服务器无法看到您的资料。 unknown status + 未知状态 No comment provided by engineer. @@ -6724,6 +7155,7 @@ SimpleX 服务器无法看到您的资料。 updated profile + 更新了资料 profile update event chat item diff --git a/apps/ios/SimpleX NSE/NotificationService.swift b/apps/ios/SimpleX NSE/NotificationService.swift index 67536d7b78..6f76781837 100644 --- a/apps/ios/SimpleX NSE/NotificationService.swift +++ b/apps/ios/SimpleX NSE/NotificationService.swift @@ -640,7 +640,9 @@ func receivedMsgNtf(_ res: ChatResponse) async -> (String, NSENotification)? { cleanupDirectFile(aChatItem) return nil case let .sndFileRcvCancelled(_, aChatItem, _): - cleanupDirectFile(aChatItem) + if let aChatItem = aChatItem { + cleanupDirectFile(aChatItem) + } return nil case let .sndFileCompleteXFTP(_, aChatItem, _): cleanupFile(aChatItem) diff --git a/apps/ios/SimpleX NSE/hu.lproj/InfoPlist.strings b/apps/ios/SimpleX NSE/hu.lproj/InfoPlist.strings new file mode 100644 index 0000000000..1a7b57b088 --- /dev/null +++ b/apps/ios/SimpleX NSE/hu.lproj/InfoPlist.strings @@ -0,0 +1,9 @@ +/* Bundle display name */ +"CFBundleDisplayName" = "SimpleX NSE"; + +/* Bundle name */ +"CFBundleName" = "SimpleX NSE"; + +/* Copyright (human-readable) */ +"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. Minden jog fenntartva."; + diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index c3851802b7..9e43249650 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -36,6 +36,11 @@ 5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFC727B2782E00FB6C6D /* BGManager.swift */; }; 5C35CFCB27B2E91D00FB6C6D /* NtfManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */; }; 5C36027327F47AD5009F19D9 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C36027227F47AD5009F19D9 /* AppDelegate.swift */; }; + 5C371E742BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E6F2BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a */; }; + 5C371E752BACC5D600100AD3 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E702BACC5D600100AD3 /* libgmpxx.a */; }; + 5C371E762BACC5D600100AD3 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E712BACC5D600100AD3 /* libffi.a */; }; + 5C371E772BACC5D600100AD3 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E722BACC5D600100AD3 /* libgmp.a */; }; + 5C371E782BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C371E732BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a */; }; 5C3A88CE27DF50170060F1C2 /* DetermineWidth.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */; }; 5C3A88D127DF57800060F1C2 /* FramedItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */; }; 5C3CCFCC2AE6BD3100C3F0C3 /* ConnectDesktopView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C3CCFCB2AE6BD3100C3F0C3 /* ConnectDesktopView.swift */; }; @@ -139,11 +144,6 @@ 5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; }; 5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */; }; 5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */; }; - 5CF4416D2B8E14EF00C52786 /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF441682B8E14EF00C52786 /* libgmpxx.a */; }; - 5CF4416E2B8E14EF00C52786 /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF441692B8E14EF00C52786 /* libffi.a */; }; - 5CF4416F2B8E14EF00C52786 /* libHSsimplex-chat-5.5.6.0-LWDDfwd7rJr1QA7wWhj0K7-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF4416A2B8E14EF00C52786 /* libHSsimplex-chat-5.5.6.0-LWDDfwd7rJr1QA7wWhj0K7-ghc9.6.3.a */; }; - 5CF441702B8E14EF00C52786 /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF4416B2B8E14EF00C52786 /* libgmp.a */; }; - 5CF441712B8E14EF00C52786 /* libHSsimplex-chat-5.5.6.0-LWDDfwd7rJr1QA7wWhj0K7.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CF4416C2B8E14EF00C52786 /* libHSsimplex-chat-5.5.6.0-LWDDfwd7rJr1QA7wWhj0K7.a */; }; 5CF9371E2B23429500E1D781 /* ConcurrentQueue.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371D2B23429500E1D781 /* ConcurrentQueue.swift */; }; 5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; }; 5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; }; @@ -185,6 +185,9 @@ 64E972072881BB22008DBC02 /* CIGroupInvitationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */; }; 64F1CC3B28B39D8600CD1FB1 /* IncognitoHelp.swift in Sources */ = {isa = PBXBuildFile; fileRef = 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */; }; 8C05382E2B39887E006436DC /* VideoUtils.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C05382D2B39887E006436DC /* VideoUtils.swift */; }; + 8C69FE7D2B8C7D2700267E38 /* AppSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */; }; + 8C7D949A2B88952700B7B9E1 /* MigrateToDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C7D94992B88952700B7B9E1 /* MigrateToDevice.swift */; }; + 8C7DF3202B7CDB0A00C886D0 /* MigrateFromDevice.swift in Sources */ = {isa = PBXBuildFile; fileRef = 8C7DF31F2B7CDB0A00C886D0 /* MigrateFromDevice.swift */; }; D7197A1829AE89660055C05A /* WebRTC in Frameworks */ = {isa = PBXBuildFile; productRef = D7197A1729AE89660055C05A /* WebRTC */; }; D72A9088294BD7A70047C86D /* NativeTextEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = D72A9087294BD7A70047C86D /* NativeTextEditor.swift */; }; D741547829AF89AF0022400A /* StoreKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D741547729AF89AF0022400A /* StoreKit.framework */; }; @@ -285,6 +288,14 @@ 5C35CFC727B2782E00FB6C6D /* BGManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGManager.swift; sourceTree = ""; }; 5C35CFCA27B2E91D00FB6C6D /* NtfManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NtfManager.swift; sourceTree = ""; }; 5C36027227F47AD5009F19D9 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + 5C371E4E2BA9AAA200100AD3 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/Localizable.strings; sourceTree = ""; }; + 5C371E4F2BA9AB6400100AD3 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = "hu.lproj/SimpleX--iOS--InfoPlist.strings"; sourceTree = ""; }; + 5C371E502BA9AB6400100AD3 /* hu */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = hu; path = hu.lproj/InfoPlist.strings; sourceTree = ""; }; + 5C371E6F2BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a"; sourceTree = ""; }; + 5C371E702BACC5D600100AD3 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5C371E712BACC5D600100AD3 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 5C371E722BACC5D600100AD3 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 5C371E732BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a"; sourceTree = ""; }; 5C3A88CD27DF50170060F1C2 /* DetermineWidth.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DetermineWidth.swift; sourceTree = ""; }; 5C3A88D027DF57800060F1C2 /* FramedItemView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FramedItemView.swift; sourceTree = ""; }; 5C3CCFCB2AE6BD3100C3F0C3 /* ConnectDesktopView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConnectDesktopView.swift; sourceTree = ""; }; @@ -426,11 +437,6 @@ 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = ""; }; 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPadding.swift; sourceTree = ""; }; 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDeliveryReceiptsView.swift; sourceTree = ""; }; - 5CF441682B8E14EF00C52786 /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 5CF441692B8E14EF00C52786 /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; - 5CF4416A2B8E14EF00C52786 /* libHSsimplex-chat-5.5.6.0-LWDDfwd7rJr1QA7wWhj0K7-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.6.0-LWDDfwd7rJr1QA7wWhj0K7-ghc9.6.3.a"; sourceTree = ""; }; - 5CF4416B2B8E14EF00C52786 /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 5CF4416C2B8E14EF00C52786 /* libHSsimplex-chat-5.5.6.0-LWDDfwd7rJr1QA7wWhj0K7.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.5.6.0-LWDDfwd7rJr1QA7wWhj0K7.a"; sourceTree = ""; }; 5CF9371D2B23429500E1D781 /* ConcurrentQueue.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConcurrentQueue.swift; sourceTree = ""; }; 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = ""; }; 5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = ""; }; @@ -473,6 +479,9 @@ 64E972062881BB22008DBC02 /* CIGroupInvitationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CIGroupInvitationView.swift; sourceTree = ""; }; 64F1CC3A28B39D8600CD1FB1 /* IncognitoHelp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IncognitoHelp.swift; sourceTree = ""; }; 8C05382D2B39887E006436DC /* VideoUtils.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = VideoUtils.swift; sourceTree = ""; }; + 8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppSettings.swift; sourceTree = ""; }; + 8C7D94992B88952700B7B9E1 /* MigrateToDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToDevice.swift; sourceTree = ""; }; + 8C7DF31F2B7CDB0A00C886D0 /* MigrateFromDevice.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateFromDevice.swift; sourceTree = ""; }; D72A9087294BD7A70047C86D /* NativeTextEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NativeTextEditor.swift; sourceTree = ""; }; D741547729AF89AF0022400A /* StoreKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = StoreKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/StoreKit.framework; sourceTree = DEVELOPER_DIR; }; D741547929AF90B00022400A /* PushKit.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = PushKit.framework; path = Platforms/iPhoneOS.platform/Developer/SDKs/iPhoneOS16.1.sdk/System/Library/Frameworks/PushKit.framework; sourceTree = DEVELOPER_DIR; }; @@ -514,13 +523,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5CF4416D2B8E14EF00C52786 /* libgmpxx.a in Frameworks */, - 5CF441712B8E14EF00C52786 /* libHSsimplex-chat-5.5.6.0-LWDDfwd7rJr1QA7wWhj0K7.a in Frameworks */, - 5CF4416F2B8E14EF00C52786 /* libHSsimplex-chat-5.5.6.0-LWDDfwd7rJr1QA7wWhj0K7-ghc9.6.3.a in Frameworks */, + 5C371E752BACC5D600100AD3 /* libgmpxx.a in Frameworks */, + 5C371E742BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, + 5C371E782BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a in Frameworks */, + 5C371E762BACC5D600100AD3 /* libffi.a in Frameworks */, + 5C371E772BACC5D600100AD3 /* libgmp.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - 5CF441702B8E14EF00C52786 /* libgmp.a in Frameworks */, - 5CF4416E2B8E14EF00C52786 /* libffi.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -553,6 +562,7 @@ 5CB924DD27A8622200ACCCDD /* NewChat */, 5CFA59C22860B04D00863A68 /* Database */, 5CB634AB29E46CDB0066AD6B /* LocalAuth */, + 8C7D94982B8894D300B7B9E1 /* Migration */, 5CA8D01B2AD9B076001FD661 /* RemoteAccess */, 5CB924DF27A8678B00ACCCDD /* UserSettings */, 5C2E261127A30FEA00F70299 /* TerminalView.swift */, @@ -582,11 +592,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 5CF441692B8E14EF00C52786 /* libffi.a */, - 5CF4416B2B8E14EF00C52786 /* libgmp.a */, - 5CF441682B8E14EF00C52786 /* libgmpxx.a */, - 5CF4416A2B8E14EF00C52786 /* libHSsimplex-chat-5.5.6.0-LWDDfwd7rJr1QA7wWhj0K7-ghc9.6.3.a */, - 5CF4416C2B8E14EF00C52786 /* libHSsimplex-chat-5.5.6.0-LWDDfwd7rJr1QA7wWhj0K7.a */, + 5C371E712BACC5D600100AD3 /* libffi.a */, + 5C371E722BACC5D600100AD3 /* libgmp.a */, + 5C371E702BACC5D600100AD3 /* libgmpxx.a */, + 5C371E6F2BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv-ghc9.6.3.a */, + 5C371E732BACC5D600100AD3 /* libHSsimplex-chat-5.6.0.4-E0iWSIg8fcR48og4na41Dv.a */, ); path = Libraries; sourceTree = ""; @@ -766,6 +776,7 @@ 64D0C2BF29F9688300B38D5F /* UserAddressView.swift */, 64D0C2C129FA57AB00B38D5F /* UserAddressLearnMore.swift */, 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */, + 8C69FE7C2B8C7D2700267E38 /* AppSettings.swift */, ); path = UserSettings; sourceTree = ""; @@ -893,6 +904,15 @@ path = Group; sourceTree = ""; }; + 8C7D94982B8894D300B7B9E1 /* Migration */ = { + isa = PBXGroup; + children = ( + 8C7DF31F2B7CDB0A00C886D0 /* MigrateFromDevice.swift */, + 8C7D94992B88952700B7B9E1 /* MigrateToDevice.swift */, + ); + path = Migration; + sourceTree = ""; + }; /* End PBXGroup section */ /* Begin PBXHeadersBuildPhase section */ @@ -1043,6 +1063,7 @@ uk, bg, tr, + hu, ); mainGroup = 5CA059BD279559F40002BEB4; packageReferences = ( @@ -1124,6 +1145,7 @@ 5CBD285A295711D700EC2CF4 /* ImageUtils.swift in Sources */, 6419EC562AB8BC8B004A607A /* ContextInvitingContactMemberView.swift in Sources */, 5CE4407927ADB701007B033A /* EmojiItemView.swift in Sources */, + 8C7D949A2B88952700B7B9E1 /* MigrateToDevice.swift in Sources */, 5C3F1D562842B68D00EC8A82 /* IntegrityErrorItemView.swift in Sources */, 5C029EAA283942EA004A9677 /* CallController.swift in Sources */, 5CBE6C142944CC12002D9531 /* ScanCodeView.swift in Sources */, @@ -1179,6 +1201,7 @@ 5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */, 5C35CFC827B2782E00FB6C6D /* BGManager.swift in Sources */, 5CB634B129E5EFEA0066AD6B /* PasscodeView.swift in Sources */, + 8C69FE7D2B8C7D2700267E38 /* AppSettings.swift in Sources */, 5C2E260F27A30FDC00F70299 /* ChatView.swift in Sources */, 5C2E260B27A30CFA00F70299 /* ChatListView.swift in Sources */, 6442E0BA287F169300CEC0F9 /* AddGroupView.swift in Sources */, @@ -1220,6 +1243,7 @@ 5CB0BA92282713FD00B3292C /* CreateProfile.swift in Sources */, 5C5F2B7027EBC704006A9D5F /* ProfileImage.swift in Sources */, 5C9329412929248A0090FFF9 /* ScanProtocolServer.swift in Sources */, + 8C7DF3202B7CDB0A00C886D0 /* MigrateFromDevice.swift in Sources */, 64AA1C6C27F3537400AC7277 /* DeletedItemView.swift in Sources */, 5C93293F2928E0FD0090FFF9 /* AudioRecPlay.swift in Sources */, 5C029EA82837DBB3004A9677 /* CICallItemView.swift in Sources */, @@ -1331,6 +1355,7 @@ 5C636F672AAB3D2400751C84 /* uk */, 5C5B67932ABAF56000DA9412 /* bg */, 5C245F3E2B501F13001CC39F /* tr */, + 5C371E502BA9AB6400100AD3 /* hu */, ); name = InfoPlist.strings; sourceTree = ""; @@ -1354,6 +1379,7 @@ 5CE6C7B42AAB1527007F345C /* uk */, 5C5B67912ABAF4B500DA9412 /* bg */, 5C245F3C2B501E98001CC39F /* tr */, + 5C371E4E2BA9AAA200100AD3 /* hu */, ); name = Localizable.strings; sourceTree = ""; @@ -1376,6 +1402,7 @@ 5C636F662AAB3D2400751C84 /* uk */, 5C5B67922ABAF56000DA9412 /* bg */, 5C245F3D2B501F13001CC39F /* tr */, + 5C371E4F2BA9AB6400100AD3 /* hu */, ); name = "SimpleX--iOS--InfoPlist.strings"; sourceTree = ""; @@ -1509,7 +1536,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 201; + CURRENT_PROJECT_VERSION = 204; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; @@ -1531,7 +1558,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 5.5.6; + MARKETING_VERSION = 5.6; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1552,7 +1579,7 @@ CLANG_ENABLE_MODULES = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 201; + CURRENT_PROJECT_VERSION = 204; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_PREVIEWS = YES; @@ -1574,7 +1601,7 @@ "$(inherited)", "@executable_path/Frameworks", ); - MARKETING_VERSION = 5.5.6; + MARKETING_VERSION = 5.6; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.app; PRODUCT_NAME = SimpleX; SDKROOT = iphoneos; @@ -1633,7 +1660,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 201; + CURRENT_PROJECT_VERSION = 204; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GENERATE_INFOPLIST_FILE = YES; @@ -1646,7 +1673,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.5.6; + MARKETING_VERSION = 5.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1665,7 +1692,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 201; + CURRENT_PROJECT_VERSION = 204; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GENERATE_INFOPLIST_FILE = YES; @@ -1678,7 +1705,7 @@ "@executable_path/Frameworks", "@executable_path/../../Frameworks", ); - MARKETING_VERSION = 5.5.6; + MARKETING_VERSION = 5.6; PRODUCT_BUNDLE_IDENTIFIER = "chat.simplex.app.SimpleX-NSE"; PRODUCT_NAME = "$(TARGET_NAME)"; PROVISIONING_PROFILE_SPECIFIER = ""; @@ -1697,7 +1724,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 201; + CURRENT_PROJECT_VERSION = 204; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1721,7 +1748,7 @@ "$(inherited)", "$(PROJECT_DIR)/Libraries/sim", ); - MARKETING_VERSION = 5.5.6; + MARKETING_VERSION = 5.6; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; @@ -1743,7 +1770,7 @@ APPLICATION_EXTENSION_API_ONLY = YES; CLANG_ENABLE_MODULES = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 201; + CURRENT_PROJECT_VERSION = 204; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1767,7 +1794,7 @@ "$(inherited)", "$(PROJECT_DIR)/Libraries/sim", ); - MARKETING_VERSION = 5.5.6; + MARKETING_VERSION = 5.6; PRODUCT_BUNDLE_IDENTIFIER = chat.simplex.SimpleXChat; PRODUCT_NAME = "$(TARGET_NAME:c99extidentifier)"; SDKROOT = iphoneos; diff --git a/apps/ios/SimpleXChat/API.swift b/apps/ios/SimpleXChat/API.swift index c0bb298929..cdd1008c13 100644 --- a/apps/ios/SimpleXChat/API.swift +++ b/apps/ios/SimpleXChat/API.swift @@ -54,6 +54,38 @@ public func chatMigrateInit(_ useKey: String? = nil, confirmMigrations: Migratio return result } +public func chatInitTemporaryDatabase(url: URL, key: String? = nil, confirmation: MigrationConfirmation = .error) -> (DBMigrationResult, chat_ctrl?) { + let dbPath = url.path + let dbKey = key ?? randomDatabasePassword() + logger.debug("chatInitTemporaryDatabase path: \(dbPath)") + var temporaryController: chat_ctrl? = nil + var cPath = dbPath.cString(using: .utf8)! + var cKey = dbKey.cString(using: .utf8)! + var cConfirm = confirmation.rawValue.cString(using: .utf8)! + let cjson = chat_migrate_init_key(&cPath, &cKey, 1, &cConfirm, 0, &temporaryController)! + return (dbMigrationResult(fromCString(cjson)), temporaryController) +} + +public func chatInitControllerRemovingDatabases() { + let dbPath = getAppDatabasePath().path + let fm = FileManager.default + // Remove previous databases, otherwise, can be .errorNotADatabase with nil controller + try? fm.removeItem(atPath: dbPath + CHAT_DB) + try? fm.removeItem(atPath: dbPath + AGENT_DB) + + let dbKey = randomDatabasePassword() + logger.debug("chatInitControllerRemovingDatabases path: \(dbPath)") + var cPath = dbPath.cString(using: .utf8)! + var cKey = dbKey.cString(using: .utf8)! + var cConfirm = MigrationConfirmation.error.rawValue.cString(using: .utf8)! + chat_migrate_init_key(&cPath, &cKey, 1, &cConfirm, 0, &chatController) + + // We need only controller, not databases + try? fm.removeItem(atPath: dbPath + CHAT_DB) + try? fm.removeItem(atPath: dbPath + AGENT_DB) +} + + public func chatCloseStore() { let err = fromCString(chat_close_store(getChatCtrl())) if err != "" { @@ -73,17 +105,17 @@ public func resetChatCtrl() { migrationResult = nil } -public func sendSimpleXCmd(_ cmd: ChatCommand) -> ChatResponse { +public func sendSimpleXCmd(_ cmd: ChatCommand, _ ctrl: chat_ctrl? = nil) -> ChatResponse { var c = cmd.cmdString.cString(using: .utf8)! - let cjson = chat_send_cmd(getChatCtrl(), &c)! + let cjson = chat_send_cmd(ctrl ?? getChatCtrl(), &c)! return chatResponse(fromCString(cjson)) } // in microseconds let MESSAGE_TIMEOUT: Int32 = 15_000_000 -public func recvSimpleXMsg() -> ChatResponse? { - if let cjson = chat_recv_msg_wait(getChatCtrl(), MESSAGE_TIMEOUT) { +public func recvSimpleXMsg(_ ctrl: chat_ctrl? = nil) -> ChatResponse? { + if let cjson = chat_recv_msg_wait(ctrl ?? getChatCtrl(), MESSAGE_TIMEOUT) { let s = fromCString(cjson) return s == "" ? nil : chatResponse(s) } @@ -181,13 +213,11 @@ public func chatResponse(_ s: String) -> ChatResponse { } } else if type == "chatCmdError" { if let jError = jResp["chatCmdError"] as? NSDictionary { - let user: UserRef? = try? decodeObject(jError["user_"] as Any) - return .chatCmdError(user_: user, chatError: .invalidJSON(json: prettyJSON(jError) ?? "")) + return .chatCmdError(user_: decodeUser_(jError), chatError: .invalidJSON(json: prettyJSON(jError) ?? "")) } } else if type == "chatError" { if let jError = jResp["chatError"] as? NSDictionary { - let user: UserRef? = try? decodeObject(jError["user_"] as Any) - return .chatError(user_: user, chatError: .invalidJSON(json: prettyJSON(jError) ?? "")) + return .chatError(user_: decodeUser_(jError), chatError: .invalidJSON(json: prettyJSON(jError) ?? "")) } } } @@ -196,6 +226,14 @@ public func chatResponse(_ s: String) -> ChatResponse { return ChatResponse.response(type: type ?? "invalid", json: json ?? s) } +private func decodeUser_(_ jDict: NSDictionary) -> UserRef? { + if let user_ = jDict["user_"] { + try? decodeObject(user_ as Any) + } else { + nil + } +} + func parseChatData(_ jChat: Any) throws -> ChatData { let jChatDict = jChat as! NSDictionary let chatInfo: ChatInfo = try decodeObject(jChatDict["chatInfo"]!) diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index 9c5aa0da62..f55c69a349 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -32,10 +32,15 @@ public enum ChatCommand { case setTempFolder(tempFolder: String) case setFilesFolder(filesFolder: String) case apiSetEncryptLocalFiles(enable: Bool) + case apiSetPQEncryption(enable: Bool) + case apiSetContactPQ(contactId: Int64, enable: Bool) case apiExportArchive(config: ArchiveConfig) case apiImportArchive(config: ArchiveConfig) case apiDeleteStorage case apiStorageEncryption(config: DBEncryptionConfig) + case testStorageEncryption(key: String) + case apiSaveSettings(settings: AppSettings) + case apiGetSettings(settings: AppSettings) case apiGetChats(userId: Int64) case apiGetChat(type: ChatType, id: Int64, pagination: ChatPagination, search: String) case apiGetChatItemInfo(type: ChatType, id: Int64, itemId: Int64) @@ -130,6 +135,9 @@ public enum ChatCommand { case listRemoteCtrls case stopRemoteCtrl case deleteRemoteCtrl(remoteCtrlId: Int64) + case apiUploadStandaloneFile(userId: Int64, file: CryptoFile) + case apiDownloadStandaloneFile(userId: Int64, url: String, file: CryptoFile) + case apiStandaloneFileInfo(url: String) // misc case showVersion case string(String) @@ -162,10 +170,15 @@ public enum ChatCommand { case let .setTempFolder(tempFolder): return "/_temp_folder \(tempFolder)" case let .setFilesFolder(filesFolder): return "/_files_folder \(filesFolder)" case let .apiSetEncryptLocalFiles(enable): return "/_files_encrypt \(onOff(enable))" + case let .apiSetPQEncryption(enable): return "/pq \(onOff(enable))" + case let .apiSetContactPQ(contactId, enable): return "/_pq @\(contactId) \(onOff(enable))" case let .apiExportArchive(cfg): return "/_db export \(encodeJSON(cfg))" case let .apiImportArchive(cfg): return "/_db import \(encodeJSON(cfg))" case .apiDeleteStorage: return "/_db delete" case let .apiStorageEncryption(cfg): return "/_db encryption \(encodeJSON(cfg))" + case let .testStorageEncryption(key): return "/db test key \(key)" + case let .apiSaveSettings(settings): return "/_save app settings \(encodeJSON(settings))" + case let .apiGetSettings(settings): return "/_get app settings \(encodeJSON(settings))" case let .apiGetChats(userId): return "/_get chats \(userId) pcc=on" case let .apiGetChat(type, id, pagination, search): return "/_get chat \(ref(type, id)) \(pagination.cmdString)" + (search == "" ? "" : " search=\(search)") @@ -278,6 +291,9 @@ public enum ChatCommand { case .listRemoteCtrls: return "/list remote ctrls" case .stopRemoteCtrl: return "/stop remote ctrl" case let .deleteRemoteCtrl(rcId): return "/delete remote ctrl \(rcId)" + case let .apiUploadStandaloneFile(userId, file): return "/_upload \(userId) \(file.filePath)" + case let .apiDownloadStandaloneFile(userId, link, file): return "/_download \(userId) \(link) \(file.filePath)" + case let .apiStandaloneFileInfo(link): return "/_download info \(link)" case .showVersion: return "/version" case let .string(str): return str } @@ -306,10 +322,15 @@ public enum ChatCommand { case .setTempFolder: return "setTempFolder" case .setFilesFolder: return "setFilesFolder" case .apiSetEncryptLocalFiles: return "apiSetEncryptLocalFiles" + case .apiSetPQEncryption: return "apiSetPQEncryption" + case .apiSetContactPQ: return "apiSetContactPQ" case .apiExportArchive: return "apiExportArchive" case .apiImportArchive: return "apiImportArchive" case .apiDeleteStorage: return "apiDeleteStorage" case .apiStorageEncryption: return "apiStorageEncryption" + case .testStorageEncryption: return "testStorageEncryption" + case .apiSaveSettings: return "apiSaveSettings" + case .apiGetSettings: return "apiGetSettings" case .apiGetChats: return "apiGetChats" case .apiGetChat: return "apiGetChat" case .apiGetChatItemInfo: return "apiGetChatItemInfo" @@ -402,6 +423,9 @@ public enum ChatCommand { case .listRemoteCtrls: return "listRemoteCtrls" case .stopRemoteCtrl: return "stopRemoteCtrl" case .deleteRemoteCtrl: return "deleteRemoteCtrl" + case .apiUploadStandaloneFile: return "apiUploadStandaloneFile" + case .apiDownloadStandaloneFile: return "apiDownloadStandaloneFile" + case .apiStandaloneFileInfo: return "apiStandaloneFileInfo" case .showVersion: return "showVersion" case .string: return "console command" } @@ -436,6 +460,8 @@ public enum ChatCommand { return .apiUnhideUser(userId: userId, viewPwd: obfuscate(viewPwd)) case let .apiDeleteUser(userId, delSMPQueues, viewPwd): return .apiDeleteUser(userId: userId, delSMPQueues: delSMPQueues, viewPwd: obfuscate(viewPwd)) + case let .testStorageEncryption(key): + return .testStorageEncryption(key: obfuscate(key)) default: return self } } @@ -584,20 +610,28 @@ public enum ChatResponse: Decodable, Error { // receiving file events case rcvFileAccepted(user: UserRef, chatItem: AChatItem) case rcvFileAcceptedSndCancelled(user: UserRef, rcvFileTransfer: RcvFileTransfer) - case rcvFileStart(user: UserRef, chatItem: AChatItem) - case rcvFileProgressXFTP(user: UserRef, chatItem: AChatItem, receivedSize: Int64, totalSize: Int64) + case standaloneFileInfo(fileMeta: MigrationFileLinkData?) + case rcvStandaloneFileCreated(user: UserRef, rcvFileTransfer: RcvFileTransfer) + case rcvFileStart(user: UserRef, chatItem: AChatItem) // send by chats + case rcvFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, receivedSize: Int64, totalSize: Int64, rcvFileTransfer: RcvFileTransfer) case rcvFileComplete(user: UserRef, chatItem: AChatItem) - case rcvFileCancelled(user: UserRef, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer) + case rcvStandaloneFileComplete(user: UserRef, targetPath: String, rcvFileTransfer: RcvFileTransfer) + case rcvFileCancelled(user: UserRef, chatItem_: AChatItem?, rcvFileTransfer: RcvFileTransfer) case rcvFileSndCancelled(user: UserRef, chatItem: AChatItem, rcvFileTransfer: RcvFileTransfer) - case rcvFileError(user: UserRef, chatItem: AChatItem) + case rcvFileError(user: UserRef, chatItem_: AChatItem?, rcvFileTransfer: RcvFileTransfer) // sending file events case sndFileStart(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) case sndFileComplete(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) - case sndFileCancelled(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta, sndFileTransfers: [SndFileTransfer]) - case sndFileRcvCancelled(user: UserRef, chatItem: AChatItem, sndFileTransfer: SndFileTransfer) - case sndFileProgressXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64) + case sndFileRcvCancelled(user: UserRef, chatItem_: AChatItem?, sndFileTransfer: SndFileTransfer) + case sndFileCancelled(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sndFileTransfers: [SndFileTransfer]) + case sndStandaloneFileCreated(user: UserRef, fileTransferMeta: FileTransferMeta) // returned by _upload + case sndFileStartXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) // not used + case sndFileProgressXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, sentSize: Int64, totalSize: Int64) + case sndFileRedirectStartXFTP(user: UserRef, fileTransferMeta: FileTransferMeta, redirectMeta: FileTransferMeta) case sndFileCompleteXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) - case sndFileError(user: UserRef, chatItem: AChatItem) + case sndStandaloneFileComplete(user: UserRef, fileTransferMeta: FileTransferMeta, rcvURIs: [String]) + case sndFileCancelledXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta) + case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta) // call events case callInvitation(callInvitation: RcvCallInvitation) case callOffer(user: UserRef, contact: Contact, callType: CallType, offer: WebRTCSession, sharedKey: String?, askConfirmation: Bool) @@ -606,7 +640,7 @@ public enum ChatResponse: Decodable, Error { case callEnded(user: UserRef, contact: Contact) case callInvitations(callInvitations: [RcvCallInvitation]) case ntfTokenStatus(status: NtfTknStatus) - case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode) + case ntfToken(token: DeviceToken, status: NtfTknStatus, ntfMode: NotificationsMode, ntfServer: String) case ntfMessages(user_: User?, connEntity_: ConnectionEntity?, msgTs: Date?, ntfMessages: [NtfMsgInfo]) case ntfMessage(user: UserRef, connEntity: ConnectionEntity, ntfMessage: NtfMsgInfo) case contactConnectionDeleted(user: UserRef, connection: PendingContactConnection) @@ -617,12 +651,16 @@ public enum ChatResponse: Decodable, Error { case remoteCtrlSessionCode(remoteCtrl_: RemoteCtrlInfo?, sessionCode: String) case remoteCtrlConnected(remoteCtrl: RemoteCtrlInfo) case remoteCtrlStopped(rcsState: RemoteCtrlSessionState, rcStopReason: RemoteCtrlStopReason) + // pq + case contactPQAllowed(user: UserRef, contact: Contact, pqEncryption: Bool) + case contactPQEnabled(user: UserRef, contact: Contact, pqEnabled: Bool) // misc case versionInfo(versionInfo: CoreVersionInfo, chatMigrations: [UpMigration], agentMigrations: [UpMigration]) case cmdOk(user: UserRef?) case chatCmdError(user_: UserRef?, chatError: ChatError) case chatError(user_: UserRef?, chatError: ChatError) case archiveImported(archiveErrors: [ArchiveError]) + case appSettings(appSettings: AppSettings) public var responseType: String { get { @@ -735,18 +773,26 @@ public enum ChatResponse: Decodable, Error { case .newMemberContactReceivedInv: return "newMemberContactReceivedInv" case .rcvFileAccepted: return "rcvFileAccepted" case .rcvFileAcceptedSndCancelled: return "rcvFileAcceptedSndCancelled" + case .standaloneFileInfo: return "standaloneFileInfo" + case .rcvStandaloneFileCreated: return "rcvStandaloneFileCreated" case .rcvFileStart: return "rcvFileStart" case .rcvFileProgressXFTP: return "rcvFileProgressXFTP" case .rcvFileComplete: return "rcvFileComplete" + case .rcvStandaloneFileComplete: return "rcvStandaloneFileComplete" case .rcvFileCancelled: return "rcvFileCancelled" case .rcvFileSndCancelled: return "rcvFileSndCancelled" case .rcvFileError: return "rcvFileError" case .sndFileStart: return "sndFileStart" case .sndFileComplete: return "sndFileComplete" case .sndFileCancelled: return "sndFileCancelled" - case .sndFileRcvCancelled: return "sndFileRcvCancelled" + case .sndStandaloneFileCreated: return "sndStandaloneFileCreated" + case .sndFileStartXFTP: return "sndFileStartXFTP" case .sndFileProgressXFTP: return "sndFileProgressXFTP" + case .sndFileRedirectStartXFTP: return "sndFileRedirectStartXFTP" + case .sndFileRcvCancelled: return "sndFileRcvCancelled" case .sndFileCompleteXFTP: return "sndFileCompleteXFTP" + case .sndStandaloneFileComplete: return "sndStandaloneFileComplete" + case .sndFileCancelledXFTP: return "sndFileCancelledXFTP" case .sndFileError: return "sndFileError" case .callInvitation: return "callInvitation" case .callOffer: return "callOffer" @@ -765,11 +811,14 @@ public enum ChatResponse: Decodable, Error { case .remoteCtrlSessionCode: return "remoteCtrlSessionCode" case .remoteCtrlConnected: return "remoteCtrlConnected" case .remoteCtrlStopped: return "remoteCtrlStopped" + case .contactPQAllowed: return "contactPQAllowed" + case .contactPQEnabled: return "contactPQAllowed" case .versionInfo: return "versionInfo" case .cmdOk: return "cmdOk" case .chatCmdError: return "chatCmdError" case .chatError: return "chatError" case .archiveImported: return "archiveImported" + case .appSettings: return "appSettings" } } } @@ -885,19 +934,27 @@ public enum ChatResponse: Decodable, Error { case let .newMemberContactReceivedInv(u, contact, groupInfo, member): return withUser(u, "contact: \(contact)\ngroupInfo: \(groupInfo)\nmember: \(member)") case let .rcvFileAccepted(u, chatItem): return withUser(u, String(describing: chatItem)) case .rcvFileAcceptedSndCancelled: return noDetails + case let .standaloneFileInfo(fileMeta): return String(describing: fileMeta) + case .rcvStandaloneFileCreated: return noDetails case let .rcvFileStart(u, chatItem): return withUser(u, String(describing: chatItem)) - case let .rcvFileProgressXFTP(u, chatItem, receivedSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nreceivedSize: \(receivedSize)\ntotalSize: \(totalSize)") + case let .rcvFileProgressXFTP(u, chatItem, receivedSize, totalSize, _): return withUser(u, "chatItem: \(String(describing: chatItem))\nreceivedSize: \(receivedSize)\ntotalSize: \(totalSize)") + case let .rcvStandaloneFileComplete(u, targetPath, _): return withUser(u, targetPath) case let .rcvFileComplete(u, chatItem): return withUser(u, String(describing: chatItem)) case let .rcvFileCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) case let .rcvFileSndCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .rcvFileError(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .rcvFileError(u, chatItem, _): return withUser(u, String(describing: chatItem)) case let .sndFileStart(u, chatItem, _): return withUser(u, String(describing: chatItem)) case let .sndFileComplete(u, chatItem, _): return withUser(u, String(describing: chatItem)) case let .sndFileCancelled(u, chatItem, _, _): return withUser(u, String(describing: chatItem)) + case .sndStandaloneFileCreated: return noDetails + case let .sndFileStartXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) case let .sndFileRcvCancelled(u, chatItem, _): return withUser(u, String(describing: chatItem)) case let .sndFileProgressXFTP(u, chatItem, _, sentSize, totalSize): return withUser(u, "chatItem: \(String(describing: chatItem))\nsentSize: \(sentSize)\ntotalSize: \(totalSize)") + case let .sndFileRedirectStartXFTP(u, _, redirectMeta): return withUser(u, String(describing: redirectMeta)) case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileError(u, chatItem): return withUser(u, String(describing: chatItem)) + case let .sndStandaloneFileComplete(u, _, rcvURIs): return withUser(u, String(rcvURIs.count)) + case let .sndFileCancelledXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileError(u, chatItem, _): return withUser(u, String(describing: chatItem)) case let .callInvitation(inv): return String(describing: inv) case let .callOffer(u, contact, callType, offer, sharedKey, askConfirmation): return withUser(u, "contact: \(contact.id)\ncallType: \(String(describing: callType))\nsharedKey: \(sharedKey ?? "")\naskConfirmation: \(askConfirmation)\noffer: \(String(describing: offer))") case let .callAnswer(u, contact, answer): return withUser(u, "contact: \(contact.id)\nanswer: \(String(describing: answer))") @@ -905,7 +962,7 @@ public enum ChatResponse: Decodable, Error { case let .callEnded(u, contact): return withUser(u, "contact: \(contact.id)") case let .callInvitations(invs): return String(describing: invs) case let .ntfTokenStatus(status): return String(describing: status) - case let .ntfToken(token, status, ntfMode): return "token: \(token)\nstatus: \(status.rawValue)\nntfMode: \(ntfMode.rawValue)" + case let .ntfToken(token, status, ntfMode, ntfServer): return "token: \(token)\nstatus: \(status.rawValue)\nntfMode: \(ntfMode.rawValue)\nntfServer: \(ntfServer)" case let .ntfMessages(u, connEntity, msgTs, ntfMessages): return withUser(u, "connEntity: \(String(describing: connEntity))\nmsgTs: \(String(describing: msgTs))\nntfMessages: \(String(describing: ntfMessages))") 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)) @@ -915,11 +972,14 @@ public enum ChatResponse: Decodable, Error { case let .remoteCtrlSessionCode(remoteCtrl_, sessionCode): return "remoteCtrl_:\n\(String(describing: remoteCtrl_))\nsessionCode: \(sessionCode)" case let .remoteCtrlConnected(remoteCtrl): return String(describing: remoteCtrl) case .remoteCtrlStopped: return noDetails + case let .contactPQAllowed(u, contact, pqEncryption): return withUser(u, "contact: \(String(describing: contact))\npqEncryption: \(pqEncryption)") + case let .contactPQEnabled(u, contact, pqEnabled): return withUser(u, "contact: \(String(describing: contact))\npqEnabled: \(pqEnabled)") case let .versionInfo(versionInfo, chatMigrations, agentMigrations): return "\(String(describing: versionInfo))\n\nchat migrations: \(chatMigrations.map(\.upName))\n\nagent migrations: \(agentMigrations.map(\.upName))" case .cmdOk: return noDetails case let .chatCmdError(u, chatError): return withUser(u, String(describing: chatError)) case let .chatError(u, chatError): return withUser(u, String(describing: chatError)) case let .archiveImported(archiveErrors): return String(describing: archiveErrors) + case let .appSettings(appSettings): return String(describing: appSettings) } } } @@ -1521,7 +1581,7 @@ public enum NotificationsMode: String, Decodable, SelectableItem { public static var values: [NotificationsMode] = [.instant, .periodic, .off] } -public enum NotificationPreviewMode: String, SelectableItem { +public enum NotificationPreviewMode: String, SelectableItem, Codable { case hidden case contact case message @@ -1721,6 +1781,7 @@ public enum StoreError: Decodable { case fileIdNotFoundBySharedMsgId(sharedMsgId: String) case sndFileNotFoundXFTP(agentSndFileId: String) case rcvFileNotFoundXFTP(agentRcvFileId: String) + case extraFileDescrNotFoundXFTP(fileId: Int64) case connectionNotFound(agentConnId: String) case connectionNotFoundById(connId: Int64) case connectionNotFoundByMemberId(groupMemberId: Int64) @@ -1882,3 +1943,147 @@ public enum RemoteCtrlError: Decodable { case badVersion(appVersion: String) // case protocolError(protocolError: RemoteProtocolError) } + +public struct MigrationFileLinkData: Codable { + let networkConfig: NetworkConfig? + + public init(networkConfig: NetworkConfig) { + self.networkConfig = networkConfig + } + + public struct NetworkConfig: Codable { + let socksProxy: String? + let hostMode: HostMode? + let requiredHostMode: Bool? + + public init(socksProxy: String?, hostMode: HostMode?, requiredHostMode: Bool?) { + self.socksProxy = socksProxy + self.hostMode = hostMode + self.requiredHostMode = requiredHostMode + } + + public func transformToPlatformSupported() -> NetworkConfig { + return if let hostMode, let requiredHostMode { + NetworkConfig( + socksProxy: nil, + hostMode: hostMode == .onionViaSocks ? .onionHost : hostMode, + requiredHostMode: requiredHostMode + ) + } else { self } + } + } + + public func addToLink(link: String) -> String { + "\(link)&data=\(encodeJSON(self).addingPercentEncoding(withAllowedCharacters: .urlHostAllowed)!)" + } + + public static func readFromLink(link: String) -> MigrationFileLinkData? { +// standaloneFileInfo(link) + nil + } +} + +public struct AppSettings: Codable, Equatable { + public var networkConfig: NetCfg? = nil + public var privacyEncryptLocalFiles: Bool? = nil + public var privacyAcceptImages: Bool? = nil + public var privacyLinkPreviews: Bool? = nil + public var privacyShowChatPreviews: Bool? = nil + public var privacySaveLastDraft: Bool? = nil + public var privacyProtectScreen: Bool? = nil + public var notificationMode: AppSettingsNotificationMode? = nil + public var notificationPreviewMode: NotificationPreviewMode? = nil + public var webrtcPolicyRelay: Bool? = nil + public var webrtcICEServers: [String]? = nil + public var confirmRemoteSessions: Bool? = nil + public var connectRemoteViaMulticast: Bool? = nil + public var connectRemoteViaMulticastAuto: Bool? = nil + public var developerTools: Bool? = nil + public var confirmDBUpgrades: Bool? = nil + public var androidCallOnLockScreen: AppSettingsLockScreenCalls? = nil + public var iosCallKitEnabled: Bool? = nil + public var iosCallKitCallsInRecents: Bool? = nil + + public func prepareForExport() -> AppSettings { + var empty = AppSettings() + let def = AppSettings.defaults + if networkConfig != def.networkConfig { empty.networkConfig = networkConfig } + if privacyEncryptLocalFiles != def.privacyEncryptLocalFiles { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } + if privacyAcceptImages != def.privacyAcceptImages { empty.privacyAcceptImages = privacyAcceptImages } + if privacyLinkPreviews != def.privacyLinkPreviews { empty.privacyLinkPreviews = privacyLinkPreviews } + if privacyShowChatPreviews != def.privacyShowChatPreviews { empty.privacyShowChatPreviews = privacyShowChatPreviews } + if privacySaveLastDraft != def.privacySaveLastDraft { empty.privacySaveLastDraft = privacySaveLastDraft } + if privacyProtectScreen != def.privacyProtectScreen { empty.privacyProtectScreen = privacyProtectScreen } + if notificationMode != def.notificationMode { empty.notificationMode = notificationMode } + if notificationPreviewMode != def.notificationPreviewMode { empty.notificationPreviewMode = notificationPreviewMode } + if webrtcPolicyRelay != def.webrtcPolicyRelay { empty.webrtcPolicyRelay = webrtcPolicyRelay } + if webrtcICEServers != def.webrtcICEServers { empty.webrtcICEServers = webrtcICEServers } + if confirmRemoteSessions != def.confirmRemoteSessions { empty.confirmRemoteSessions = confirmRemoteSessions } + if connectRemoteViaMulticast != def.connectRemoteViaMulticast {empty.connectRemoteViaMulticast = connectRemoteViaMulticast } + if connectRemoteViaMulticastAuto != def.connectRemoteViaMulticastAuto { empty.connectRemoteViaMulticastAuto = connectRemoteViaMulticastAuto } + if developerTools != def.developerTools { empty.developerTools = developerTools } + if confirmDBUpgrades != def.confirmDBUpgrades { empty.confirmDBUpgrades = confirmDBUpgrades } + if androidCallOnLockScreen != def.androidCallOnLockScreen { empty.androidCallOnLockScreen = androidCallOnLockScreen } + if iosCallKitEnabled != def.iosCallKitEnabled { empty.iosCallKitEnabled = iosCallKitEnabled } + if iosCallKitCallsInRecents != def.iosCallKitCallsInRecents { empty.iosCallKitCallsInRecents = iosCallKitCallsInRecents } + return empty + } + + public static var defaults: AppSettings { + AppSettings ( + networkConfig: NetCfg.defaults, + privacyEncryptLocalFiles: true, + privacyAcceptImages: true, + privacyLinkPreviews: true, + privacyShowChatPreviews: true, + privacySaveLastDraft: true, + privacyProtectScreen: false, + notificationMode: AppSettingsNotificationMode.instant, + notificationPreviewMode: NotificationPreviewMode.message, + webrtcPolicyRelay: true, + webrtcICEServers: [], + confirmRemoteSessions: false, + connectRemoteViaMulticast: true, + connectRemoteViaMulticastAuto: true, + developerTools: false, + confirmDBUpgrades: false, + androidCallOnLockScreen: AppSettingsLockScreenCalls.show, + iosCallKitEnabled: true, + iosCallKitCallsInRecents: false + ) + } +} + +public enum AppSettingsNotificationMode: String, Codable { + case off + case periodic + case instant + + public func toNotificationsMode() -> NotificationsMode { + switch self { + case .instant: .instant + case .periodic: .periodic + case .off: .off + } + } + + public static func from(_ mode: NotificationsMode) -> AppSettingsNotificationMode { + switch mode { + case .instant: .instant + case .periodic: .periodic + case .off: .off + } + } +} + +//public enum NotificationPreviewMode: Codable { +// case hidden +// case contact +// case message +//} + +public enum AppSettingsLockScreenCalls: String, Codable { + case disable + case show + case accept +} diff --git a/apps/ios/SimpleXChat/AppGroup.swift b/apps/ios/SimpleXChat/AppGroup.swift index ceb7d9d7db..4fbe78dc7a 100644 --- a/apps/ios/SimpleXChat/AppGroup.swift +++ b/apps/ios/SimpleXChat/AppGroup.swift @@ -19,6 +19,7 @@ public let GROUP_DEFAULT_CHAT_LAST_BACKGROUND_RUN = "chatLastBackgroundRun" let GROUP_DEFAULT_NTF_PREVIEW_MODE = "ntfPreviewMode" public let GROUP_DEFAULT_NTF_ENABLE_LOCAL = "ntfEnableLocal" // no longer used public let GROUP_DEFAULT_NTF_ENABLE_PERIODIC = "ntfEnablePeriodic" // no longer used +// This setting is a main one, while having an unused duplicate from the past: DEFAULT_PRIVACY_ACCEPT_IMAGES let GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES = "privacyAcceptImages" public let GROUP_DEFAULT_PRIVACY_TRANSFER_IMAGES_INLINE = "privacyTransferImagesInline" // no longer used public let GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES = "privacyEncryptLocalFiles" @@ -36,9 +37,10 @@ let GROUP_DEFAULT_NETWORK_TCP_KEEP_INTVL = "networkTCPKeepIntvl" let GROUP_DEFAULT_NETWORK_TCP_KEEP_CNT = "networkTCPKeepCnt" public let GROUP_DEFAULT_INCOGNITO = "incognito" let GROUP_DEFAULT_STORE_DB_PASSPHRASE = "storeDBPassphrase" -let GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE = "initialRandomDBPassphrase" +public let GROUP_DEFAULT_INITIAL_RANDOM_DB_PASSPHRASE = "initialRandomDBPassphrase" public let GROUP_DEFAULT_CONFIRM_DB_UPGRADES = "confirmDBUpgrades" public let GROUP_DEFAULT_CALL_KIT_ENABLED = "callKitEnabled" +public let GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED = "pqExperimentalEnabled" public let APP_GROUP_NAME = "group.chat.simplex.app" @@ -67,6 +69,7 @@ public func registerGroupDefaults() { GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES: true, GROUP_DEFAULT_CONFIRM_DB_UPGRADES: false, GROUP_DEFAULT_CALL_KIT_ENABLED: true, + GROUP_DEFAULT_PQ_EXPERIMENTAL_ENABLED: false, ]) } @@ -167,6 +170,7 @@ public let ntfPreviewModeGroupDefault = EnumDefault( public let incognitoGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_INCOGNITO) +// This setting is a main one, while having an unused duplicate from the past: DEFAULT_PRIVACY_ACCEPT_IMAGES public let privacyAcceptImagesGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ACCEPT_IMAGES) public let privacyEncryptLocalFilesGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES) @@ -193,6 +197,8 @@ public let confirmDBUpgradesGroupDefault = BoolDefault(defaults: groupDefaults, public let callKitEnabledGroupDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_CALL_KIT_ENABLED) +public let pqExperimentalEnabledDefault = BoolDefault(defaults: groupDefaults, forKey: GROUP_DEFAULT_PRIVACY_ENCRYPT_LOCAL_FILES) + public class DateDefault { var defaults: UserDefaults var key: String diff --git a/apps/ios/SimpleXChat/ChatTypes.swift b/apps/ios/SimpleXChat/ChatTypes.swift index 198a777f8b..b74a2517c7 100644 --- a/apps/ios/SimpleXChat/ChatTypes.swift +++ b/apps/ios/SimpleXChat/ChatTypes.swift @@ -1532,22 +1532,32 @@ public struct Connection: Decodable { public var viaGroupLink: Bool public var customUserProfileId: Int64? public var connectionCode: SecurityCode? + public var pqSupport: Bool + public var pqEncryption: Bool + public var pqSndEnabled: Bool? + public var pqRcvEnabled: Bool? public var connectionStats: ConnectionStats? = nil private enum CodingKeys: String, CodingKey { - case connId, agentConnId, peerChatVRange, connStatus, connLevel, viaGroupLink, customUserProfileId, connectionCode + case connId, agentConnId, peerChatVRange, connStatus, connLevel, viaGroupLink, customUserProfileId, connectionCode, pqSupport, pqEncryption, pqSndEnabled, pqRcvEnabled } public var id: ChatId { get { ":\(connId)" } } + public var connPQEnabled: Bool { + pqSndEnabled == true && pqRcvEnabled == true + } + static let sampleData = Connection( connId: 1, agentConnId: "abc", peerChatVRange: VersionRange(minVersion: 1, maxVersion: 1), connStatus: .ready, connLevel: 0, - viaGroupLink: false + viaGroupLink: false, + pqSupport: false, + pqEncryption: false ) } @@ -2268,7 +2278,7 @@ public struct ChatItem: Identifiable, Decodable { case .rcvDirectEvent(rcvDirectEvent: let rcvDirectEvent): switch rcvDirectEvent { case .contactDeleted: return false - case .profileUpdated: return true + case .profileUpdated: return false } case .rcvGroupEvent(rcvGroupEvent: let rcvGroupEvent): switch rcvGroupEvent { @@ -2300,6 +2310,10 @@ public struct ChatItem: Identifiable, Decodable { case .sndModerated: return false case .rcvModerated: return false case .rcvBlocked: return false + case .sndDirectE2EEInfo: return false + case .rcvDirectE2EEInfo: return false + case .sndGroupE2EEInfo: return false + case .rcvGroupE2EEInfo: return false case .invalidJSON: return false } } @@ -2735,6 +2749,10 @@ public enum CIContent: Decodable, ItemContent { case sndModerated case rcvModerated case rcvBlocked + case sndDirectE2EEInfo(e2eeInfo: E2EEInfo) + case rcvDirectE2EEInfo(e2eeInfo: E2EEInfo) + case sndGroupE2EEInfo(e2eeInfo: E2EEInfo) + case rcvGroupE2EEInfo(e2eeInfo: E2EEInfo) case invalidJSON(json: String) public var text: String { @@ -2766,11 +2784,25 @@ public enum CIContent: Decodable, ItemContent { case .sndModerated: return NSLocalizedString("moderated", comment: "moderated chat item") case .rcvModerated: return NSLocalizedString("moderated", comment: "moderated chat item") case .rcvBlocked: return NSLocalizedString("blocked by admin", comment: "blocked chat item") + case let .sndDirectE2EEInfo(e2eeInfo): return directE2EEInfoStr(e2eeInfo) + case let .rcvDirectE2EEInfo(e2eeInfo): return directE2EEInfoStr(e2eeInfo) + case .sndGroupE2EEInfo: return e2eeInfoNoPQStr + case .rcvGroupE2EEInfo: return e2eeInfoNoPQStr case .invalidJSON: return NSLocalizedString("invalid data", comment: "invalid chat item") } } } + private func directE2EEInfoStr(_ e2eeInfo: E2EEInfo) -> String { + e2eeInfo.pqEnabled + ? NSLocalizedString("This chat is protected by quantum resistant end-to-end encryption.", comment: "E2EE info chat item") + : e2eeInfoNoPQStr + } + + private var e2eeInfoNoPQStr: String { + NSLocalizedString("This chat is protected by end-to-end encryption.", comment: "E2EE info chat item") + } + static func featureText(_ feature: Feature, _ enabled: String, _ param: Int?) -> String { feature.hasParam ? "\(feature.text): \(timeText(param))" @@ -3378,11 +3410,14 @@ public struct SndFileTransfer: Decodable { } public struct RcvFileTransfer: Decodable { - + public let fileId: Int64 } public struct FileTransferMeta: Decodable { - + public let fileId: Int64 + public let fileName: String + public let filePath: String + public let fileSize: Int64 } public enum CICallStatus: String, Decodable { @@ -3457,6 +3492,10 @@ public enum CIGroupInvitationStatus: String, Decodable { case expired } +public struct E2EEInfo: Decodable { + public var pqEnabled: Bool +} + public enum RcvDirectEvent: Decodable { case contactDeleted case profileUpdated(fromProfile: Profile, toProfile: Profile) @@ -3574,7 +3613,8 @@ public enum RcvConnEvent: Decodable { case switchQueue(phase: SwitchPhase) case ratchetSync(syncStatus: RatchetSyncState) case verificationCodeReset - + case pqEnabled(enabled: Bool) + var text: String { switch self { case let .switchQueue(phase): @@ -3586,6 +3626,12 @@ public enum RcvConnEvent: Decodable { return ratchetSyncStatusToText(syncStatus) case .verificationCodeReset: return NSLocalizedString("security code changed", comment: "chat item text") + case let .pqEnabled(enabled): + if enabled { + return NSLocalizedString("quantum resistant e2e encryption", comment: "chat item text") + } else { + return NSLocalizedString("standard end-to-end encryption", comment: "chat item text") + } } } } @@ -3603,6 +3649,7 @@ func ratchetSyncStatusToText(_ ratchetSyncStatus: RatchetSyncState) -> String { public enum SndConnEvent: Decodable { case switchQueue(phase: SwitchPhase, member: GroupMemberRef?) case ratchetSync(syncStatus: RatchetSyncState, member: GroupMemberRef?) + case pqEnabled(enabled: Bool) var text: String { switch self { @@ -3626,6 +3673,12 @@ public enum SndConnEvent: Decodable { } } return ratchetSyncStatusToText(syncStatus) + case let .pqEnabled(enabled): + if enabled { + return NSLocalizedString("quantum resistant e2e encryption", comment: "chat item text") + } else { + return NSLocalizedString("standard end-to-end encryption", comment: "chat item text") + } } } } diff --git a/apps/ios/SimpleXChat/FileUtils.swift b/apps/ios/SimpleXChat/FileUtils.swift index 7496bf7215..125600f3f3 100644 --- a/apps/ios/SimpleXChat/FileUtils.swift +++ b/apps/ios/SimpleXChat/FileUtils.swift @@ -28,9 +28,9 @@ public let MAX_FILE_SIZE_SMP: Int64 = 8000000 public let MAX_VOICE_MESSAGE_LENGTH = TimeInterval(300) -private let CHAT_DB: String = "_chat.db" +let CHAT_DB: String = "_chat.db" -private let AGENT_DB: String = "_agent.db" +let AGENT_DB: String = "_agent.db" private let CHAT_DB_BAK: String = "_chat.db.bak" @@ -83,6 +83,7 @@ public func deleteAppDatabaseAndFiles() { try? fm.removeItem(atPath: dbPath + CHAT_DB_BAK) try? fm.removeItem(atPath: dbPath + AGENT_DB_BAK) try? fm.removeItem(at: getTempFilesDirectory()) + try? fm.removeItem(at: getMigrationTempFilesDirectory()) try? fm.createDirectory(at: getTempFilesDirectory(), withIntermediateDirectories: true) deleteAppFiles() _ = kcDatabasePassword.remove() @@ -183,6 +184,10 @@ public func getTempFilesDirectory() -> URL { getAppDirectory().appendingPathComponent("temp_files", isDirectory: true) } +public func getMigrationTempFilesDirectory() -> URL { + getDocumentsDirectory().appendingPathComponent("migration_temp_files", isDirectory: true) +} + public func getAppFilesDirectory() -> URL { getAppDirectory().appendingPathComponent("app_files", isDirectory: true) } diff --git a/apps/ios/SimpleXChat/hs_init.c b/apps/ios/SimpleXChat/hs_init.c index 83056fccfe..adacd57310 100644 --- a/apps/ios/SimpleXChat/hs_init.c +++ b/apps/ios/SimpleXChat/hs_init.c @@ -15,7 +15,7 @@ void haskell_init(void) { char *argv[] = { "simplex", "+RTS", // requires `hs_init_with_rtsopts` - "-A16m", // chunk size for new allocations + "-A64m", // chunk size for new allocations "-H64m", // initial heap size "-xn", // non-moving GC 0 diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 4d0ee65e46..580c20d65a 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -202,6 +202,9 @@ /* No comment provided by engineer. */ "%lld messages blocked" = "%lld блокирани съобщения"; +/* No comment provided by engineer. */ +"%lld messages blocked by admin" = "%lld съобщения, блокирани от администратора"; + /* No comment provided by engineer. */ "%lld messages marked deleted" = "%lld съобщения, маркирани като изтрити"; @@ -398,6 +401,9 @@ /* No comment provided by engineer. */ "All group members will remain connected." = "Всички членове на групата ще останат свързани."; +/* No comment provided by engineer. */ +"All messages will be deleted - this cannot be undone!" = "Всички съобщения ще бъдат изтрити - това не може да бъде отменено!"; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Всички съобщения ще бъдат изтрити - това не може да бъде отменено! Съобщенията ще бъдат изтрити САМО за вас."; @@ -581,6 +587,9 @@ /* No comment provided by engineer. */ "Block" = "Блокирай"; +/* No comment provided by engineer. */ +"Block for all" = "Блокирай за всички"; + /* No comment provided by engineer. */ "Block group members" = "Блокиране на членове на групата"; @@ -588,11 +597,23 @@ "Block member" = "Блокирай член"; /* No comment provided by engineer. */ -"Block member?" = "Блокирай члена?"; +"Block member for all?" = "Блокиране на член за всички?"; /* No comment provided by engineer. */ +"Block member?" = "Блокирай члена?"; + +/* marked deleted chat item preview text */ "blocked" = "блокиран"; +/* rcv group event chat item */ +"blocked %@" = "блокиран %@"; + +/* marked deleted chat item preview text */ +"blocked by admin" = "блокиран от админ"; + +/* No comment provided by engineer. */ +"Blocked by admin" = "Блокиран от админ"; + /* No comment provided by engineer. */ "bold" = "удебелен"; @@ -685,7 +706,7 @@ "Change self-destruct passcode" = "Промени кода за достъп за самоунищожение"; /* chat item text */ -"changed address for you" = "променен е адреса за вас"; +"changed address for you" = "адреса за изпращане е променен"; /* rcv group event chat item */ "changed role of %@ to %@" = "променена роля от %1$@ на %2$@"; @@ -750,6 +771,9 @@ /* No comment provided by engineer. */ "Clear conversation?" = "Изчисти разговора?"; +/* No comment provided by engineer. */ +"Clear private notes?" = "Изчистване на лични бележки?"; + /* No comment provided by engineer. */ "Clear verification" = "Изчисти проверката"; @@ -888,6 +912,9 @@ /* connection information */ "connection:%@" = "връзка:%@"; +/* profile update event chat item */ +"contact %@ changed to %@" = "името на контакта %1$@ е променено на %2$@"; + /* No comment provided by engineer. */ "Contact allows" = "Контактът позволява"; @@ -972,6 +999,12 @@ /* No comment provided by engineer. */ "Create your profile" = "Създай своя профил"; +/* No comment provided by engineer. */ +"Created at" = "Създаден на"; + +/* copied message info */ +"Created at: %@" = "Създаден на: %@"; + /* No comment provided by engineer. */ "Created on %@" = "Създаден на %@"; @@ -1291,7 +1324,7 @@ "Discover and join groups" = "Открийте и се присъединете към групи"; /* No comment provided by engineer. */ -"Discover via local network" = "Открий през локалната мрежа"; +"Discover via local network" = "Откриване през локалната мрежа"; /* No comment provided by engineer. */ "Do it later" = "Отложи"; @@ -1524,6 +1557,9 @@ /* No comment provided by engineer. */ "Error creating member contact" = "Грешка при създаване на контакт с член"; +/* No comment provided by engineer. */ +"Error creating message" = "Грешка при създаване на съобщение"; + /* No comment provided by engineer. */ "Error creating profile!" = "Грешка при създаване на профил!"; @@ -1938,6 +1974,9 @@ /* No comment provided by engineer. */ "Import database" = "Импортиране на база данни"; +/* No comment provided by engineer. */ +"Improved message delivery" = "Подобрена доставка на съобщения"; + /* No comment provided by engineer. */ "Improved privacy and security" = "Подобрена поверителност и сигурност"; @@ -2115,6 +2154,9 @@ /* No comment provided by engineer. */ "Join group" = "Влез в групата"; +/* No comment provided by engineer. */ +"Join group conversations" = "Присъединяване към групи"; + /* No comment provided by engineer. */ "Join group?" = "Влез в групата?"; @@ -2250,6 +2292,9 @@ /* No comment provided by engineer. */ "Member" = "Член"; +/* profile update event chat item */ +"member %@ changed to %@" = "името на члена %1$@ е променено на %2$@"; + /* rcv group event chat item */ "member connected" = "свързан"; @@ -2328,7 +2373,7 @@ /* copied message info */ "Moderated at: %@" = "Модерирано в: %@"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "модерирано от %@"; /* time unit */ @@ -2594,12 +2639,18 @@ /* No comment provided by engineer. */ "Password to show" = "Парола за показване"; +/* past/unknown group member */ +"Past member %@" = "Бивш член %@"; + /* No comment provided by engineer. */ "Paste desktop address" = "Постави адрес на настолно устройство"; /* No comment provided by engineer. */ "Paste image" = "Постави изображение"; +/* No comment provided by engineer. */ +"Paste link to connect!" = "Поставете линк, за да се свържете!"; + /* No comment provided by engineer. */ "Paste the link you received" = "Постави получения линк"; @@ -2687,6 +2738,9 @@ /* No comment provided by engineer. */ "Private filenames" = "Поверителни имена на файлове"; +/* name of notes to self */ +"Private notes" = "Лични бележки"; + /* No comment provided by engineer. */ "Profile and server connections" = "Профилни и сървърни връзки"; @@ -2801,6 +2855,9 @@ /* No comment provided by engineer. */ "Receiving via" = "Получаване чрез"; +/* No comment provided by engineer. */ +"Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." = "Скорошна история и подобрен [bot за директория за групи](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPd jdLW3%23%2F%3Fv%3D1-2% 26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)."; + /* No comment provided by engineer. */ "Recipients see updates as you type them." = "Получателите виждат актуализации, докато ги въвеждате."; @@ -2855,6 +2912,12 @@ /* rcv group event chat item */ "removed %@" = "отстранен %@"; +/* profile update event chat item */ +"removed contact address" = "премахнат адрес за контакт"; + +/* profile update event chat item */ +"removed profile picture" = "премахната профилна снимка"; + /* rcv group event chat item */ "removed you" = "ви острани"; @@ -2978,6 +3041,9 @@ /* No comment provided by engineer. */ "Save welcome message?" = "Запази съобщението при посрещане?"; +/* message info title */ +"Saved message" = "Запазено съобщение"; + /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "Запазените WebRTC ICE сървъри ще бъдат премахнати"; @@ -2999,6 +3065,9 @@ /* No comment provided by engineer. */ "Search" = "Търсене"; +/* No comment provided by engineer. */ +"Search bar accepts invitation links." = "Лентата за търсене приема линк за връзка."; + /* No comment provided by engineer. */ "Search or paste SimpleX link" = "Търсене или поставяне на SimpleX линк"; @@ -3155,6 +3224,12 @@ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Задайте го вместо системната идентификация."; +/* profile update event chat item */ +"set new contact address" = "зададен нов адрес за контакт"; + +/* profile update event chat item */ +"set new profile picture" = "зададена нова профилна снимка"; + /* No comment provided by engineer. */ "Set passcode" = "Задай kод за достъп"; @@ -3524,6 +3599,9 @@ /* No comment provided by engineer. */ "Trying to connect to the server used to receive messages from this contact." = "Опит за свързване със сървъра, използван за получаване на съобщения от този контакт."; +/* No comment provided by engineer. */ +"Turkish interface" = "Турски интерфейс"; + /* No comment provided by engineer. */ "Turn off" = "Изключи"; @@ -3536,12 +3614,21 @@ /* No comment provided by engineer. */ "Unblock" = "Отблокирай"; +/* No comment provided by engineer. */ +"Unblock for all" = "Отблокирай за всички"; + /* No comment provided by engineer. */ "Unblock member" = "Отблокирай член"; +/* No comment provided by engineer. */ +"Unblock member for all?" = "Отблокиране на член за всички?"; + /* No comment provided by engineer. */ "Unblock member?" = "Отблокирай член?"; +/* rcv group event chat item */ +"unblocked %@" = "отблокиран %@"; + /* item status description */ "Unexpected error: %@" = "Неочаквана грешка: %@"; @@ -3575,6 +3662,9 @@ /* No comment provided by engineer. */ "Unknown error" = "Непозната грешка"; +/* No comment provided by engineer. */ +"unknown status" = "неизвестен статус"; + /* No comment provided by engineer. */ "Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "Освен ако не използвате интерфейса за повикване на iOS, активирайте режима \"Не безпокой\", за да избегнете прекъсвания."; @@ -3620,6 +3710,9 @@ /* rcv group event chat item */ "updated group profile" = "актуализиран профил на групата"; +/* profile update event chat item */ +"updated profile" = "актуализиран профил"; + /* No comment provided by engineer. */ "Updating settings will re-connect the client to all servers." = "Актуализирането на настройките ще свърже отново клиента към всички сървъри."; @@ -3678,19 +3771,19 @@ "v%@ (%@)" = "v%@ (%@)"; /* No comment provided by engineer. */ -"Verify code with desktop" = "Потвръди кода с настолното устройство"; +"Verify code with desktop" = "Потвърди кода с настолното устройство"; /* No comment provided by engineer. */ -"Verify connection" = "Потвръди връзките"; +"Verify connection" = "Потвърди връзка"; /* No comment provided by engineer. */ -"Verify connection security" = "Потвръди сигурността на връзката"; +"Verify connection security" = "Потвърди сигурността на връзката"; /* No comment provided by engineer. */ -"Verify connections" = "Потвръди връзките"; +"Verify connections" = "Потвърждение за свързване"; /* No comment provided by engineer. */ -"Verify security code" = "Потвръди кода за сигурност"; +"Verify security code" = "Потвърди кода за сигурност"; /* No comment provided by engineer. */ "Via browser" = "Чрез браузър"; @@ -3794,9 +3887,15 @@ /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Когато споделяте инкогнито профил с някого, този профил ще се използва за групите, в които той ви кани."; +/* No comment provided by engineer. */ +"With encrypted files and media." = "С криптирани файлове и медия."; + /* No comment provided by engineer. */ "With optional welcome message." = "С незадължително съобщение при посрещане."; +/* No comment provided by engineer. */ +"With reduced battery usage." = "С намален разход на батерията."; + /* No comment provided by engineer. */ "Wrong database passphrase" = "Грешна парола за базата данни"; @@ -3857,6 +3956,9 @@ /* No comment provided by engineer. */ "you are observer" = "вие сте наблюдател"; +/* snd group event chat item */ +"you blocked %@" = "вие блокирахте %@"; + /* No comment provided by engineer. */ "You can accept calls from lock screen, without device and app authentication." = "Можете да приемате обаждания от заключен екран, без идентификация на устройство и приложението."; @@ -3906,10 +4008,10 @@ "You can't send messages!" = "Не може да изпращате съобщения!"; /* chat item text */ -"you changed address" = "променихте адреса"; +"you changed address" = "адреса за получаване е променен"; /* chat item text */ -"you changed address for %@" = "променихте адреса за %@"; +"you changed address for %@" = "променихте адреса получаване за %@"; /* snd group event chat item */ "you changed role for yourself to %@" = "променихте ролята си на %@"; @@ -3968,6 +4070,9 @@ /* chat list item description */ "you shared one-time link incognito" = "споделихте еднократен инкогнито линк за връзка"; +/* snd group event chat item */ +"you unblocked %@" = "вие отблокирахте %@"; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "Ще бъдете свързани с групата, когато устройството на домакина на групата е онлайн, моля, изчакайте или проверете по-късно!"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index 9d8bd1e4a9..a777a33613 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -2070,7 +2070,7 @@ /* copied message info */ "Moderated at: %@" = "Upraveno v: %@"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "moderovaný %@"; /* time unit */ diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 3bc7690c8d..7074a4ee27 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Beste Privatsphäre**: Es wird kein SimpleX-Chat-Benachrichtigungs-Server genutzt, Nachrichten werden in periodischen Abständen im Hintergrund geprüft (dies hängt davon ab, wie häufig Sie die App nutzen)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Bitte beachten Sie**: Aus Sicherheitsgründen wird die Nachrichtenentschlüsselung Ihrer Verbindungen abgebrochen, wenn Sie die gleiche Datenbank auf zwei Geräten nutzen."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Bitte beachten Sie**: Das Passwort kann NICHT wiederhergestellt oder geändert werden, wenn Sie es vergessen haben oder verlieren."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Warnung**: Sofortige Push-Benachrichtigungen erfordern die Eingabe eines Passworts, welches in Ihrem Schlüsselbund gespeichert ist."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Warnung**: Das Archiv wird gelöscht."; + /* No comment provided by engineer. */ "*bold*" = "\\*fett*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ wurde mit Ihnen verbunden"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ heruntergeladen"; + /* notification title */ "%@ is connected!" = "%@ ist mit Ihnen verbunden!"; @@ -148,6 +157,9 @@ /* No comment provided by engineer. */ "%@ servers" = "%@-Server"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ hochgeladen"; + /* notification title */ "%@ wants to connect!" = "%@ will sich mit Ihnen verbinden!"; @@ -377,6 +389,9 @@ /* member role */ "admin" = "Admin"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Administratoren können für ein Mitglied alle Funktionen blockieren."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Administratoren können Links für den Beitritt zu Gruppen erzeugen."; @@ -416,6 +431,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Alle Ihre Kontakte bleiben verbunden. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Alle Ihre Kontakte, Unterhaltungen und Dateien werden sicher verschlüsselt und in Daten-Paketen auf die konfigurierten XTFP-Server hochgeladen."; + /* No comment provided by engineer. */ "Allow" = "Erlauben"; @@ -497,6 +515,9 @@ /* No comment provided by engineer. */ "App build: %@" = "App Build: %@"; +/* No comment provided by engineer. */ +"App data migration" = "App-Daten-Migration"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Neue lokale Dateien (außer Video-Dateien) werden von der App verschlüsselt."; @@ -518,6 +539,15 @@ /* No comment provided by engineer. */ "Appearance" = "Erscheinungsbild"; +/* No comment provided by engineer. */ +"Apply" = "Anwenden"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Archivieren und Hochladen"; + +/* No comment provided by engineer. */ +"Archiving database" = "Datenbank wird archiviert"; + /* No comment provided by engineer. */ "Attach" = "Anhängen"; @@ -602,13 +632,13 @@ /* No comment provided by engineer. */ "Block member?" = "Mitglied blockieren?"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "blocked" = "Blockiert"; /* rcv group event chat item */ "blocked %@" = "%@ wurde blockiert"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "wurde vom Administrator blockiert"; /* No comment provided by engineer. */ @@ -665,6 +695,9 @@ /* No comment provided by engineer. */ "Cancel" = "Abbrechen"; +/* No comment provided by engineer. */ +"Cancel migration" = "Migration abbrechen"; + /* feature offered item */ "cancelled %@" = "abgebrochen %@"; @@ -744,6 +777,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Der Chat ist angehalten. Wenn Sie diese Datenbank bereits auf einem anderen Gerät genutzt haben, sollten Sie diese vor dem Starten des Chats wieder zurückspielen."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Chat wurde migriert!"; + /* No comment provided by engineer. */ "Chat preferences" = "Chat-Präferenzen"; @@ -756,6 +792,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Chinesische und spanische Bedienoberfläche"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Wählen Sie auf dem neuen Gerät _Von einem anderen Gerät migrieren_ und scannen Sie den QR-Code."; + /* No comment provided by engineer. */ "Choose file" = "Datei auswählen"; @@ -801,6 +840,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Datenbank-Aktualisierungen bestätigen"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Bestätigen Sie die Netzwerkeinstellungen"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Neues Passwort bestätigen…"; @@ -810,6 +852,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Passwort bestätigen"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Für die Migration bestätigen Sie bitte, dass Sie sich an das Datenbank-Passwort erinnern."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Hochladen bestätigen"; + /* server test step */ "Connect" = "Verbinden"; @@ -913,7 +961,7 @@ "connection:%@" = "Verbindung:%@"; /* profile update event chat item */ -"contact %@ changed to %@" = "Der Kontaktname %1$@ wurde auf %2$@ geändert"; +"contact %@ changed to %@" = "Der Kontaktname wurde von %1$@ auf %2$@ geändert"; /* No comment provided by engineer. */ "Contact allows" = "Der Kontakt erlaubt"; @@ -1008,6 +1056,9 @@ /* No comment provided by engineer. */ "Created on %@" = "Erstellt am %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "Archiv-Link erzeugen"; + /* No comment provided by engineer. */ "Creating link…" = "Link wird erstellt…"; @@ -1155,6 +1206,9 @@ /* No comment provided by engineer. */ "Delete database" = "Datenbank löschen"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Datenbank auf diesem Gerät löschen"; + /* server test step */ "Delete file" = "Datei löschen"; @@ -1347,9 +1401,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Datenbank herabstufen und den Chat öffnen"; +/* No comment provided by engineer. */ +"Download failed" = "Herunterladen fehlgeschlagen"; + /* server test step */ "Download file" = "Datei herunterladen"; +/* No comment provided by engineer. */ +"Downloading archive" = "Archiv wird heruntergeladen"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Link-Details werden heruntergeladen"; + /* No comment provided by engineer. */ "Duplicate display name!" = "Doppelter Anzeigename!"; @@ -1383,6 +1446,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Für Alle aktivieren"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Kann in direkten Chats aktiviert werden (BETA)!"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Sofortige Benachrichtigungen aktivieren?"; @@ -1497,6 +1563,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Zugangscode eingeben"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Passwort eingeben"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Passwort eingeben…"; @@ -1536,6 +1605,9 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Fehler beim Hinzufügen von Mitgliedern"; +/* No comment provided by engineer. */ +"Error allowing contact PQ encryption" = "Fehler beim Zulassen der Kontakt-PQ-Verschlüsselung"; + /* No comment provided by engineer. */ "Error changing address" = "Fehler beim Wechseln der Empfängeradresse"; @@ -1590,6 +1662,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Fehler beim Löschen des Benutzerprofils"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Fehler beim Herunterladen des Archivs"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "Fehler beim Aktivieren von Empfangsbestätigungen!"; @@ -1635,6 +1710,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Fehler beim Speichern des Passworts in den Schlüsselbund"; +/* when migrating */ +"Error saving settings" = "Fehler beim Abspeichern der Einstellungen"; + /* No comment provided by engineer. */ "Error saving user password" = "Fehler beim Speichern des Benutzer-Passworts"; @@ -1677,6 +1755,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Fehler beim Aktualisieren der Benutzer-Privatsphäre"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Fehler beim Hochladen des Archivs"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Fehler bei der Überprüfung des Passworts:"; + /* No comment provided by engineer. */ "Error: " = "Fehler: "; @@ -1710,6 +1794,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Exportiertes Datenbankarchiv."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "Die exportierte Datei ist nicht vorhanden"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Exportieren des Datenbank-Archivs…"; @@ -1752,6 +1839,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Nach ungelesenen und favorisierten Chats filtern."; +/* No comment provided by engineer. */ +"Finalize migration" = "Die Migration wird abgeschlossen"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Die Migration auf dem anderen Gerät wird abgeschlossen."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "Endlich haben wir sie! 🚀"; @@ -1935,6 +2028,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "Wie Sie Ihre Server nutzen"; +/* No comment provided by engineer. */ +"Hungarian interface" = "Ungarische Bedienoberfläche"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "ICE-Server (einer pro Zeile)"; @@ -1974,6 +2070,12 @@ /* No comment provided by engineer. */ "Import database" = "Datenbank importieren"; +/* No comment provided by engineer. */ +"Import failed" = "Import ist fehlgeschlagen"; + +/* No comment provided by engineer. */ +"Importing archive" = "Archiv wird importiert"; + /* No comment provided by engineer. */ "Improved message delivery" = "Verbesserte Zustellung von Nachrichten"; @@ -1983,6 +2085,9 @@ /* No comment provided by engineer. */ "Improved server configuration" = "Verbesserte Serverkonfiguration"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "Um fortzufahren, sollte der Chat beendet werden."; + /* No comment provided by engineer. */ "In reply to" = "Als Antwort auf"; @@ -2067,6 +2172,9 @@ /* No comment provided by engineer. */ "Invalid link" = "Ungültiger Link"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Migrations-Bestätigung ungültig"; + /* No comment provided by engineer. */ "Invalid name!" = "Ungültiger Name!"; @@ -2293,7 +2401,7 @@ "Member" = "Mitglied"; /* profile update event chat item */ -"member %@ changed to %@" = "Der Mitgliedsname %1$@ wurde auf %2$@ geändert"; +"member %@ changed to %@" = "Der Mitgliedsname von %1$@ wurde auf %2$@ geändert"; /* rcv group event chat item */ "member connected" = "ist der Gruppe beigetreten"; @@ -2331,6 +2439,9 @@ /* No comment provided by engineer. */ "Message text" = "Nachrichtentext"; +/* No comment provided by engineer. */ +"Message too large" = "Die Nachricht ist zu lang"; + /* No comment provided by engineer. */ "Messages" = "Nachrichten"; @@ -2340,9 +2451,36 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Die Nachrichten von %@ werden angezeigt!"; +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Nachrichten, Dateien und Anrufe sind durch **Ende-zu-Ende-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt."; + +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Nachrichten, Dateien und Anrufe sind durch **Quantum-resistente E2E-Verschlüsselung** mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt."; + +/* No comment provided by engineer. */ +"Migrate device" = "Gerät migrieren"; + +/* No comment provided by engineer. */ +"Migrate from another device" = "Von einem anderen Gerät migrieren"; + +/* No comment provided by engineer. */ +"Migrate here" = "Hierher migrieren"; + +/* No comment provided by engineer. */ +"Migrate to another device" = "Auf ein anderes Gerät migrieren"; + +/* No comment provided by engineer. */ +"Migrate to another device via QR code." = "Über einen QR-Code auf ein anderes Gerät migrieren."; + +/* No comment provided by engineer. */ +"Migrating" = "Migrieren"; + /* No comment provided by engineer. */ "Migrating database archive…" = "Datenbank-Archiv wird migriert…"; +/* No comment provided by engineer. */ +"Migration complete" = "Migration abgeschlossen"; + /* No comment provided by engineer. */ "Migration error:" = "Fehler bei der Migration:"; @@ -2373,7 +2511,7 @@ /* copied message info */ "Moderated at: %@" = "Moderiert um: %@"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "Von %@ moderiert"; /* time unit */ @@ -2600,6 +2738,9 @@ /* No comment provided by engineer. */ "Open group" = "Gruppe öffnen"; +/* authentication reason */ +"Open migration to another device" = "Migration auf ein anderes Gerät öffnen"; + /* No comment provided by engineer. */ "Open Settings" = "Geräte-Einstellungen öffnen"; @@ -2612,9 +2753,15 @@ /* No comment provided by engineer. */ "Opening app…" = "App wird geöffnet…"; +/* No comment provided by engineer. */ +"Or paste archive link" = "Oder fügen Sie den Archiv-Link ein"; + /* No comment provided by engineer. */ "Or scan QR code" = "Oder den QR-Code scannen"; +/* No comment provided by engineer. */ +"Or securely share this file link" = "Oder teilen Sie diesen Datei-Link sicher"; + /* No comment provided by engineer. */ "Or show this code" = "Oder diesen QR-Code anzeigen"; @@ -2666,6 +2813,9 @@ /* message decrypt error item */ "Permanent decryption error" = "Entschlüsselungsfehler"; +/* No comment provided by engineer. */ +"Picture-in-picture calls" = "Bild-in-Bild-Anrufe"; + /* No comment provided by engineer. */ "PING count" = "PING-Zähler"; @@ -2684,6 +2834,9 @@ /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Bitte überprüfen sie sowohl Ihre, als auch die Präferenzen Ihres Kontakts."; +/* No comment provided by engineer. */ +"Please confirm that network settings are correct for this device." = "Bitte bestätigen Sie, dass die Netzwerkeinstellungen auf diesem Gerät richtig sind."; + /* No comment provided by engineer. */ "Please contact developers.\nError: %@" = "Bitte nehmen Sie Kontakt mit den Entwicklern auf.\nFehler: %@"; @@ -2717,6 +2870,9 @@ /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Der Fingerabdruck des Zertifikats in der Serveradresse ist wahrscheinlich ungültig"; +/* No comment provided by engineer. */ +"Post-quantum E2EE" = "Post-Quantum E2E-Verschlüsselung"; + /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Den letzten Nachrichtenentwurf, auch mit seinen Anhängen, aufbewahren."; @@ -2798,6 +2954,15 @@ /* No comment provided by engineer. */ "Push notifications" = "Push-Benachrichtigungen"; +/* No comment provided by engineer. */ +"Push server" = "Push-Server"; + +/* chat item text */ +"quantum resistant e2e encryption" = "Quantum-resistente E2E-Verschlüsselung"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "Quantum-resistente Verschlüsselung"; + /* No comment provided by engineer. */ "Rate the app" = "Bewerten Sie die App"; @@ -2913,10 +3078,10 @@ "removed %@" = "hat %@ aus der Gruppe entfernt"; /* profile update event chat item */ -"removed contact address" = "Kontaktadresse wurde entfernt"; +"removed contact address" = "Die Kontaktadresse wurde entfernt"; /* profile update event chat item */ -"removed profile picture" = "Profil-Bild wurde entfernt"; +"removed profile picture" = "Das Profil-Bild wurde entfernt"; /* rcv group event chat item */ "removed you" = "hat Sie aus der Gruppe entfernt"; @@ -2933,9 +3098,18 @@ /* No comment provided by engineer. */ "Repeat connection request?" = "Verbindungsanfrage wiederholen?"; +/* No comment provided by engineer. */ +"Repeat download" = "Herunterladen wiederholen"; + +/* No comment provided by engineer. */ +"Repeat import" = "Import wiederholen"; + /* No comment provided by engineer. */ "Repeat join request?" = "Verbindungsanfrage wiederholen?"; +/* No comment provided by engineer. */ +"Repeat upload" = "Hochladen wiederholen"; + /* chat item action */ "Reply" = "Antwort"; @@ -2993,6 +3167,9 @@ /* No comment provided by engineer. */ "Run chat" = "Chat starten"; +/* No comment provided by engineer. */ +"Safer groups" = "Sicherere Gruppen"; + /* chat item action */ "Save" = "Speichern"; @@ -3066,7 +3243,7 @@ "Search" = "Suche"; /* No comment provided by engineer. */ -"Search bar accepts invitation links." = "Von der Suchleiste werden Einladungslinks akzeptiert."; +"Search bar accepts invitation links." = "In der Suchleiste werden nun auch Einladungslinks akzeptiert."; /* No comment provided by engineer. */ "Search or paste SimpleX link" = "Suchen oder fügen Sie den SimpleX-Link ein"; @@ -3225,14 +3402,17 @@ "Set it instead of system authentication." = "Anstelle der System-Authentifizierung festlegen."; /* profile update event chat item */ -"set new contact address" = "Neue Kontaktadresse wurde festgelegt"; +"set new contact address" = "Es wurde eine neue Kontaktadresse festgelegt"; /* profile update event chat item */ -"set new profile picture" = "Neues Profil-Bild wurde festgelegt"; +"set new profile picture" = "Es wurde ein neues Profil-Bild festgelegt"; /* No comment provided by engineer. */ "Set passcode" = "Zugangscode einstellen"; +/* No comment provided by engineer. */ +"Set passphrase" = "Passwort festlegen"; + /* No comment provided by engineer. */ "Set passphrase to export" = "Passwort für den Export festlegen"; @@ -3278,6 +3458,9 @@ /* No comment provided by engineer. */ "Show preview" = "Vorschau anzeigen"; +/* No comment provided by engineer. */ +"Show QR code" = "QR-Code anzeigen"; + /* No comment provided by engineer. */ "Show:" = "Anzeigen:"; @@ -3338,6 +3521,9 @@ /* notification title */ "Somebody" = "Jemand"; +/* chat item text */ +"standard end-to-end encryption" = "Standard-Ende-zu-Ende-Verschlüsselung"; + /* No comment provided by engineer. */ "Start chat" = "Starten Sie den Chat"; @@ -3353,6 +3539,9 @@ /* No comment provided by engineer. */ "Stop" = "Beenden"; +/* No comment provided by engineer. */ +"Stop chat" = "Chat beenden"; + /* No comment provided by engineer. */ "Stop chat to enable database actions" = "Chat beenden, um Datenbankaktionen zu erlauben"; @@ -3380,6 +3569,9 @@ /* authentication reason */ "Stop SimpleX" = "Stoppen Sie SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "Chat wird beendet"; + /* No comment provided by engineer. */ "strike" = "durchstreichen"; @@ -3530,6 +3722,12 @@ /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Diese Aktion kann nicht rückgängig gemacht werden! Ihr Profil und Ihre Kontakte, Nachrichten und Dateien gehen unwiderruflich verloren."; +/* E2EE info chat item */ +"This chat is protected by end-to-end encryption." = "Dieser Chat ist durch Ende-zu-Ende-Verschlüsselung geschützt."; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "Dieser Chat ist durch Quantum-resistente Ende-zu-Ende-Verschlüsselung geschützt."; + /* notification title */ "this contact" = "Dieser Kontakt"; @@ -3722,9 +3920,15 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Aktualisieren und den Chat öffnen"; +/* No comment provided by engineer. */ +"Upload failed" = "Hochladen fehlgeschlagen"; + /* server test step */ "Upload file" = "Datei hochladen"; +/* No comment provided by engineer. */ +"Uploading archive" = "Archiv wird hochgeladen"; + /* No comment provided by engineer. */ "Use .onion hosts" = "Verwende .onion-Hosts"; @@ -3755,6 +3959,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Verwenden Sie SimpleX-Chat-Server?"; +/* No comment provided by engineer. */ +"Use the app while in the call." = "Die App kann während eines Anrufs genutzt werden."; + /* No comment provided by engineer. */ "User profile" = "Benutzerprofil"; @@ -3782,6 +3989,12 @@ /* No comment provided by engineer. */ "Verify connections" = "Verbindungen überprüfen"; +/* No comment provided by engineer. */ +"Verify database passphrase" = "Überprüfen Sie das Datenbank-Passwort"; + +/* No comment provided by engineer. */ +"Verify passphrase" = "Überprüfen Sie das Passwort"; + /* No comment provided by engineer. */ "Verify security code" = "Sicherheitscode überprüfen"; @@ -3860,6 +4073,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "möchte sich mit Ihnen verbinden!"; +/* No comment provided by engineer. */ +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Warnung: Das Starten des Chats auf mehreren Geräten wird nicht unterstützt und wird zu Fehlern bei der Nachrichtenübermittlung führen"; + /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Warnung: Sie könnten einige Daten verlieren!"; @@ -3875,6 +4091,9 @@ /* No comment provided by engineer. */ "Welcome message" = "Begrüßungsmeldung"; +/* No comment provided by engineer. */ +"Welcome message is too long" = "Die Begrüßungsmeldung ist zu lang"; + /* No comment provided by engineer. */ "What's new" = "Was ist neu"; @@ -3911,6 +4130,9 @@ /* No comment provided by engineer. */ "You" = "Profil"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "Sie dürfen die selbe Datenbank **nicht** auf zwei Geräten nutzen."; + /* No comment provided by engineer. */ "You accepted connection" = "Sie haben die Verbindung akzeptiert"; @@ -3971,6 +4193,9 @@ /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "Sie können diese später in den Datenschutz & Sicherheits-Einstellungen der App aktivieren."; +/* No comment provided by engineer. */ +"You can give another try." = "Sie können es nochmal probieren."; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "Sie können ein Benutzerprofil verbergen oder stummschalten - wischen Sie es nach rechts."; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 25ca04f24d..7a60858bc1 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Más privado**: no se usa el servidor de notificaciones de SimpleX Chat, los mensajes se comprueban periódicamente en segundo plano (dependiendo de la frecuencia con la que utilices la aplicación)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Tenga en cuenta**: usar la misma base de datos en dos dispositivos interrumpirá el descifrado de mensajes de sus conexiones, como protección de seguridad."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Atención**: NO podrás recuperar o cambiar la contraseña si la pierdes."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Advertencia**: Las notificaciones automáticas instantáneas requieren una contraseña guardada en Keychain."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Atención**: el archivo será eliminado."; + /* No comment provided by engineer. */ "*bold*" = "\\*bold*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ conectado"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ downloaded"; + /* notification title */ "%@ is connected!" = "%@ ¡está conectado!"; @@ -202,6 +211,9 @@ /* No comment provided by engineer. */ "%lld messages blocked" = "%lld mensaje(s) bloqueado(s)"; +/* No comment provided by engineer. */ +"%lld messages blocked by admin" = "%lld mensajes bloqueados por el administrador"; + /* No comment provided by engineer. */ "%lld messages marked deleted" = "%lld mensaje(s) marcado(s) eliminado(s)"; @@ -374,6 +386,9 @@ /* member role */ "admin" = "administrador"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Los admins pueden bloquear un miembro por todos."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Los administradores pueden crear enlaces para unirse a grupos."; @@ -398,6 +413,9 @@ /* No comment provided by engineer. */ "All group members will remain connected." = "Todos los miembros del grupo permanecerán conectados."; +/* No comment provided by engineer. */ +"All messages will be deleted - this cannot be undone!" = "Todos los mensajes serán borrados. ¡No podrá deshacerse!"; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Se eliminarán todos los mensajes SOLO para tí. ¡No podrá deshacerse!"; @@ -410,6 +428,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Todos tus contactos permanecerán conectados. La actualización del perfil se enviará a tus contactos."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Todos sus contactos, conversaciones y archivos se cifrarán de forma segura y se subirán en fragmentos hacia puntos XFTP configurados."; + /* No comment provided by engineer. */ "Allow" = "Se permite"; @@ -491,6 +512,9 @@ /* No comment provided by engineer. */ "App build: %@" = "Compilación app: %@"; +/* No comment provided by engineer. */ +"App data migration" = "Migración de datos de la aplicación"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Cifrado de los nuevos archivos locales (excepto vídeos)."; @@ -512,6 +536,15 @@ /* No comment provided by engineer. */ "Appearance" = "Apariencia"; +/* No comment provided by engineer. */ +"Apply" = "Aplicar"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Archivar y transferir"; + +/* No comment provided by engineer. */ +"Archiving database" = "Archivando base de datos"; + /* No comment provided by engineer. */ "Attach" = "Adjuntar"; @@ -581,6 +614,9 @@ /* No comment provided by engineer. */ "Block" = "Bloquear"; +/* No comment provided by engineer. */ +"Block for all" = "Bloquear para todos"; + /* No comment provided by engineer. */ "Block group members" = "Bloquear miembros del grupo"; @@ -588,11 +624,23 @@ "Block member" = "Bloquear miembro"; /* No comment provided by engineer. */ -"Block member?" = "¿Bloquear miembro?"; +"Block member for all?" = "¿Bloqear miembro para todos?"; /* No comment provided by engineer. */ +"Block member?" = "¿Bloquear miembro?"; + +/* marked deleted chat item preview text */ "blocked" = "bloqueado"; +/* rcv group event chat item */ +"blocked %@" = "%@ bloqueado"; + +/* marked deleted chat item preview text */ +"blocked by admin" = "bloqueado por el administrador"; + +/* No comment provided by engineer. */ +"Blocked by admin" = "Bloqueado por el administrador"; + /* No comment provided by engineer. */ "bold" = "negrita"; @@ -644,6 +692,9 @@ /* No comment provided by engineer. */ "Cancel" = "Cancelar"; +/* No comment provided by engineer. */ +"Cancel migration" = "Cancelar migración"; + /* feature offered item */ "cancelled %@" = "cancelado %@"; @@ -723,6 +774,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Chat está detenido. Si estás usando esta base de datos en otro dispositivo, deberías transferirla de vuelta antes de iniciarlo."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Chat transferido !"; + /* No comment provided by engineer. */ "Chat preferences" = "Preferencias de Chat"; @@ -735,6 +789,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Interfaz en chino y español"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Elija _Migrar desde otro dispositivo_ en el nuevo dispositivo y escanee el código QR."; + /* No comment provided by engineer. */ "Choose file" = "Elije archivo"; @@ -750,6 +807,9 @@ /* No comment provided by engineer. */ "Clear conversation?" = "¿Vaciar conversación?"; +/* No comment provided by engineer. */ +"Clear private notes?" = "¿Borrar notas privadas?"; + /* No comment provided by engineer. */ "Clear verification" = "Eliminar verificación"; @@ -777,6 +837,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Confirmar actualizaciones de la bases de datos"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Confirmar los ajustes de red"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Confirme nueva contraseña…"; @@ -786,6 +849,9 @@ /* No comment provided by engineer. */ "Confirm password" = "Confirmar contraseña"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Confirme que recuerda la frase secreta de la base de datos para migrarla."; + /* server test step */ "Connect" = "Conectar"; @@ -888,6 +954,9 @@ /* connection information */ "connection:%@" = "conexión: % @"; +/* profile update event chat item */ +"contact %@ changed to %@" = "el contacto %1$@ ha cambiado a %2$@"; + /* No comment provided by engineer. */ "Contact allows" = "El contacto permite"; @@ -972,6 +1041,12 @@ /* No comment provided by engineer. */ "Create your profile" = "Crea tu perfil"; +/* No comment provided by engineer. */ +"Created at" = "Creado"; + +/* copied message info */ +"Created at: %@" = "Creado: %@"; + /* No comment provided by engineer. */ "Created on %@" = "Creado en %@"; @@ -1296,6 +1371,9 @@ /* No comment provided by engineer. */ "Do it later" = "Hacer más tarde"; +/* No comment provided by engineer. */ +"Do not send history to new members." = "No enviar historial a miembros nuevos."; + /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "NO uses SimpleX para llamadas de emergencia."; @@ -1521,6 +1599,9 @@ /* No comment provided by engineer. */ "Error creating member contact" = "Error al establecer contacto con el miembro"; +/* No comment provided by engineer. */ +"Error creating message" = "Error al crear mensaje"; + /* No comment provided by engineer. */ "Error creating profile!" = "¡Error al crear perfil!"; @@ -1875,6 +1956,9 @@ /* No comment provided by engineer. */ "History" = "Historial"; +/* No comment provided by engineer. */ +"History is not sent to new members." = "El historial no se envía a miembros nuevos."; + /* time unit */ "hours" = "horas"; @@ -1932,6 +2016,9 @@ /* No comment provided by engineer. */ "Import database" = "Importar base de datos"; +/* No comment provided by engineer. */ +"Improved message delivery" = "Entrega de mensajes mejorada"; + /* No comment provided by engineer. */ "Improved privacy and security" = "Seguridad y privacidad mejoradas"; @@ -2016,6 +2103,9 @@ /* invalid chat item */ "invalid data" = "datos no válidos"; +/* No comment provided by engineer. */ +"Invalid display name!" = "¡Nombre mostrado no válido!"; + /* No comment provided by engineer. */ "Invalid link" = "Enlace no válido"; @@ -2106,6 +2196,9 @@ /* No comment provided by engineer. */ "Join group" = "Unirte al grupo"; +/* No comment provided by engineer. */ +"Join group conversations" = "Unirse a la conversación del grupo"; + /* No comment provided by engineer. */ "Join group?" = "¿Unirte al grupo?"; @@ -2241,6 +2334,9 @@ /* No comment provided by engineer. */ "Member" = "Miembro"; +/* profile update event chat item */ +"member %@ changed to %@" = "el miembro %1$@ ha cambiado a %2$@"; + /* rcv group event chat item */ "member connected" = "conectado"; @@ -2319,7 +2415,7 @@ /* copied message info */ "Moderated at: %@" = "Moderado: %@"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "moderado por %@"; /* time unit */ @@ -2585,12 +2681,18 @@ /* No comment provided by engineer. */ "Password to show" = "Contraseña para hacerlo visible"; +/* past/unknown group member */ +"Past member %@" = "Miembro pasado %@"; + /* No comment provided by engineer. */ "Paste desktop address" = "Pegar dirección de ordenador"; /* No comment provided by engineer. */ "Paste image" = "Pegar imagen"; +/* No comment provided by engineer. */ +"Paste link to connect!" = "Pegar enlace para conectar!"; + /* No comment provided by engineer. */ "Paste the link you received" = "Pegar el enlace recibido"; @@ -2678,6 +2780,9 @@ /* No comment provided by engineer. */ "Private filenames" = "Nombres de archivos privados"; +/* name of notes to self */ +"Private notes" = "Notas privadas"; + /* No comment provided by engineer. */ "Profile and server connections" = "Perfil y conexiones de servidor"; @@ -2792,6 +2897,9 @@ /* No comment provided by engineer. */ "Receiving via" = "Recibiendo vía"; +/* No comment provided by engineer. */ +"Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." = "Historial reciente y [bot del directorio](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) mejorados."; + /* No comment provided by engineer. */ "Recipients see updates as you type them." = "Los destinatarios ven actualizarse mientras escribes."; @@ -2846,6 +2954,12 @@ /* rcv group event chat item */ "removed %@" = "ha expulsado a %@"; +/* profile update event chat item */ +"removed contact address" = "dirección de contacto eliminada"; + +/* profile update event chat item */ +"removed profile picture" = "imagen de perfil eliminada"; + /* rcv group event chat item */ "removed you" = "te ha expulsado"; @@ -2969,6 +3083,9 @@ /* No comment provided by engineer. */ "Save welcome message?" = "¿Guardar mensaje de bienvenida?"; +/* message info title */ +"Saved message" = "Mensaje guardado"; + /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "Los servidores WebRTC ICE guardados serán eliminados"; @@ -2990,6 +3107,9 @@ /* No comment provided by engineer. */ "Search" = "Buscar"; +/* No comment provided by engineer. */ +"Search bar accepts invitation links." = "La barra de búsqueda acepta enlaces de invitación."; + /* No comment provided by engineer. */ "Search or paste SimpleX link" = "Buscar o pegar enlace SimpleX"; @@ -3071,6 +3191,9 @@ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Envíalos desde la galería o desde teclados personalizados."; +/* No comment provided by engineer. */ +"Send up to 100 last messages to new members." = "Enviar hasta 100 últimos mensajes a los miembros nuevos."; + /* No comment provided by engineer. */ "Sender cancelled file transfer." = "El remitente ha cancelado la transferencia de archivos."; @@ -3143,6 +3266,12 @@ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Úsalo en lugar de la autenticación del sistema."; +/* profile update event chat item */ +"set new contact address" = "nueva dirección de contacto"; + +/* profile update event chat item */ +"set new profile picture" = "nueva imagen de perfil"; + /* No comment provided by engineer. */ "Set passcode" = "Código autodestrucción"; @@ -3435,13 +3564,13 @@ "They can be overridden in contact and group settings." = "Se pueden anular en la configuración de contactos."; /* No comment provided by engineer. */ -"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Esta acción no se puede deshacer. Se eliminarán todos los archivos y multimedia recibidos y enviados. Las imágenes de baja resolución permanecerán."; +"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Esta acción es irreversible. Se eliminarán todos los archivos y multimedia recibidos y enviados. Las imágenes de baja resolución permanecerán."; /* No comment provided by engineer. */ -"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Esta acción no se puede deshacer. Se eliminarán los mensajes enviados y recibidos anteriores a la selección. Puede tardar varios minutos."; +"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "Esta acción es irreversible. Se eliminarán los mensajes enviados y recibidos anteriores a la selección. Puede tardar varios minutos."; /* No comment provided by engineer. */ -"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Esta acción no se puede deshacer. Tu perfil, contactos, mensajes y archivos se perderán irreversiblemente."; +"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Esta acción es irreversible. Tu perfil, contactos, mensajes y archivos se perderán irreversiblemente."; /* notification title */ "this contact" = "este contacto"; @@ -3449,6 +3578,9 @@ /* No comment provided by engineer. */ "This device name" = "Nombre del dispositivo"; +/* No comment provided by engineer. */ +"This display name is invalid. Please choose another name." = "Éste nombre mostrado no es válido. Por favor, elije otro nombre."; + /* No comment provided by engineer. */ "This group has over %lld members, delivery receipts are not sent." = "Este grupo tiene más de %lld miembros, no se enviarán confirmaciones de entrega."; @@ -3509,6 +3641,9 @@ /* No comment provided by engineer. */ "Trying to connect to the server used to receive messages from this contact." = "Intentando conectar con el servidor usado para recibir mensajes de este contacto."; +/* No comment provided by engineer. */ +"Turkish interface" = "Interfaz en turco"; + /* No comment provided by engineer. */ "Turn off" = "Desactivar"; @@ -3521,12 +3656,21 @@ /* No comment provided by engineer. */ "Unblock" = "Desbloquear"; +/* No comment provided by engineer. */ +"Unblock for all" = "Desbloquear para todos"; + /* No comment provided by engineer. */ "Unblock member" = "Desbloquear miembro"; +/* No comment provided by engineer. */ +"Unblock member for all?" = "¿Desbloquear miembro para todos?"; + /* No comment provided by engineer. */ "Unblock member?" = "¿Desbloquear miembro?"; +/* rcv group event chat item */ +"unblocked %@" = "%@ desbloqueado"; + /* item status description */ "Unexpected error: %@" = "Error inesperado: %@"; @@ -3560,6 +3704,9 @@ /* No comment provided by engineer. */ "Unknown error" = "Error desconocido"; +/* No comment provided by engineer. */ +"unknown status" = "estado desconocido"; + /* No comment provided by engineer. */ "Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "A menos que utilices la interfaz de llamadas de iOS, activa el modo No molestar para evitar interrupciones."; @@ -3584,6 +3731,9 @@ /* No comment provided by engineer. */ "Unread" = "No leído"; +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new members." = "Hasta 100 últimos mensajes son enviados a los miembros nuevos."; + /* No comment provided by engineer. */ "Update" = "Actualizar"; @@ -3602,6 +3752,9 @@ /* rcv group event chat item */ "updated group profile" = "ha actualizado el perfil del grupo"; +/* profile update event chat item */ +"updated profile" = "perfil actualizado"; + /* No comment provided by engineer. */ "Updating settings will re-connect the client to all servers." = "Al actualizar la configuración el cliente se reconectará a todos los servidores."; @@ -3710,6 +3863,9 @@ /* No comment provided by engineer. */ "View security code" = "Mostrar código de seguridad"; +/* chat feature */ +"Visible history" = "Historial visible"; + /* No comment provided by engineer. */ "Voice message…" = "Mensaje de voz…"; @@ -3773,9 +3929,15 @@ /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Cuando compartes un perfil incógnito con alguien, este perfil también se usará para los grupos a los que te inviten."; +/* No comment provided by engineer. */ +"With encrypted files and media." = "Con cifrado de archivos y multimedia."; + /* No comment provided by engineer. */ "With optional welcome message." = "Con mensaje de bienvenida opcional."; +/* No comment provided by engineer. */ +"With reduced battery usage." = "Con uso reducido de batería."; + /* No comment provided by engineer. */ "Wrong database passphrase" = "Contraseña de base de datos incorrecta"; @@ -3836,6 +3998,9 @@ /* No comment provided by engineer. */ "you are observer" = "Tu rol es observador"; +/* snd group event chat item */ +"you blocked %@" = "has bloqueado a %@"; + /* No comment provided by engineer. */ "You can accept calls from lock screen, without device and app authentication." = "Puede aceptar llamadas desde la pantalla de bloqueo, sin autenticación de dispositivos y aplicaciones."; @@ -3947,6 +4112,9 @@ /* chat list item description */ "you shared one-time link incognito" = "has compartido enlace de un solo uso en modo incógnito"; +/* snd group event chat item */ +"you unblocked %@" = "has desbloqueado a %@"; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "Te conectarás al grupo cuando el dispositivo del anfitrión esté en línea, por favor espera o compruébalo más tarde."; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index 2478658ace..ddd085141b 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -2046,7 +2046,7 @@ /* copied message info */ "Moderated at: %@" = "Moderoitu klo: %@"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "%@ moderoi"; /* time unit */ diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index fb202c4f2f..8a66611d39 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Confidentiel** : ne pas utiliser le serveur de notifications SimpleX, vérification de nouveaux messages periodiquement en arrière plan (dépend de l'utilisation de l'app)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Remarque** : l'utilisation de la même base de données sur deux appareils interrompt le déchiffrement des messages provenant de vos connexions, par mesure de sécurité."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Veuillez noter** : vous NE pourrez PAS récupérer ou modifier votre phrase secrète si vous la perdez."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Avertissement** : les notifications push instantanées nécessitent une phrase secrète enregistrée dans la keychain."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Avertissement** : l'archive sera supprimée."; + /* No comment provided by engineer. */ "*bold*" = "\\*gras*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ connecté(e)"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ téléchargé"; + /* notification title */ "%@ is connected!" = "%@ est connecté·e !"; @@ -148,6 +157,9 @@ /* No comment provided by engineer. */ "%@ servers" = "Serveurs %@"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ envoyé"; + /* notification title */ "%@ wants to connect!" = "%@ veut se connecter !"; @@ -202,6 +214,9 @@ /* No comment provided by engineer. */ "%lld messages blocked" = "%lld messages bloqués"; +/* No comment provided by engineer. */ +"%lld messages blocked by admin" = "%lld messages bloqués par l'administrateur"; + /* No comment provided by engineer. */ "%lld messages marked deleted" = "%lld messages marqués comme supprimés"; @@ -374,6 +389,9 @@ /* member role */ "admin" = "admin"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Les admins peuvent bloquer un membre pour tous."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Les admins peuvent créer les liens qui permettent de rejoindre les groupes."; @@ -381,10 +399,10 @@ "Advanced network settings" = "Paramètres réseau avancés"; /* chat item text */ -"agreeing encryption for %@…" = "accord sur le chiffrement pour %@…"; +"agreeing encryption for %@…" = "négociation du chiffrement avec %@…"; /* chat item text */ -"agreeing encryption…" = "accord sur le chiffrement…"; +"agreeing encryption…" = "négociation du chiffrement…"; /* No comment provided by engineer. */ "All app data is deleted." = "Toutes les données de l'application sont supprimées."; @@ -413,6 +431,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Tous vos contacts resteront connectés. La mise à jour du profil sera envoyée à vos contacts."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Tous vos contacts, conversations et fichiers seront chiffrés en toute sécurité et transférés par morceaux vers les relais XFTP configurés."; + /* No comment provided by engineer. */ "Allow" = "Autoriser"; @@ -494,6 +515,9 @@ /* No comment provided by engineer. */ "App build: %@" = "Build de l'app : %@"; +/* No comment provided by engineer. */ +"App data migration" = "Transfert des données de l'application"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "L'application chiffre les nouveaux fichiers locaux (sauf les vidéos)."; @@ -515,6 +539,15 @@ /* No comment provided by engineer. */ "Appearance" = "Apparence"; +/* No comment provided by engineer. */ +"Apply" = "Appliquer"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Archiver et transférer"; + +/* No comment provided by engineer. */ +"Archiving database" = "Archivage de la base de données"; + /* No comment provided by engineer. */ "Attach" = "Attacher"; @@ -584,6 +617,9 @@ /* No comment provided by engineer. */ "Block" = "Bloquer"; +/* No comment provided by engineer. */ +"Block for all" = "Bloqué pour tous"; + /* No comment provided by engineer. */ "Block group members" = "Bloquer des membres d'un groupe"; @@ -591,11 +627,23 @@ "Block member" = "Bloquer ce membre"; /* No comment provided by engineer. */ -"Block member?" = "Bloquer ce membre ?"; +"Block member for all?" = "Bloquer le membre pour tous ?"; /* No comment provided by engineer. */ +"Block member?" = "Bloquer ce membre ?"; + +/* marked deleted chat item preview text */ "blocked" = "blocké"; +/* rcv group event chat item */ +"blocked %@" = "%@ bloqué"; + +/* marked deleted chat item preview text */ +"blocked by admin" = "bloqué par l'administrateur"; + +/* No comment provided by engineer. */ +"Blocked by admin" = "Bloqué par l'administrateur"; + /* No comment provided by engineer. */ "bold" = "gras"; @@ -647,6 +695,9 @@ /* No comment provided by engineer. */ "Cancel" = "Annuler"; +/* No comment provided by engineer. */ +"Cancel migration" = "Annuler le transfert"; + /* feature offered item */ "cancelled %@" = "annulé %@"; @@ -688,7 +739,7 @@ "Change self-destruct passcode" = "Modifier le code d'autodestruction"; /* chat item text */ -"changed address for you" = "adresse modifiée pour vous"; +"changed address for you" = "changement de l'adresse du contact"; /* rcv group event chat item */ "changed role of %@ to %@" = "a modifié le rôle de %1$@ pour %2$@"; @@ -726,6 +777,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Le chat est arrêté. Si vous avez déjà utilisé cette base de données sur un autre appareil, vous devez la transférer à nouveau avant de démarrer le chat."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Messagerie transférée !"; + /* No comment provided by engineer. */ "Chat preferences" = "Préférences de chat"; @@ -738,6 +792,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Interface en chinois et en espagnol"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Choisissez _Transferer depuis un autre appareil_ sur le nouvel appareil et scannez le code QR."; + /* No comment provided by engineer. */ "Choose file" = "Choisir le fichier"; @@ -783,6 +840,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Confirmer la mise à niveau de la base de données"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Confirmer les paramètres réseau"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Confirmer la nouvelle phrase secrète…"; @@ -792,6 +852,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Confirmer le mot de passe"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Confirmer que vous vous souvenez de la phrase secrète de la base de données pour la transférer."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Confirmer la transmission"; + /* server test step */ "Connect" = "Se connecter"; @@ -949,7 +1015,7 @@ "Create a group using a random profile." = "Création de groupes via un profil aléatoire."; /* No comment provided by engineer. */ -"Create an address to let people connect with you." = "Créez une adresse pour permettre aux gens de vous contacter."; +"Create an address to let people connect with you." = "Vous pouvez créer une adresse pour permettre aux autres utilisateurs de vous contacter."; /* server test step */ "Create file" = "Créer un fichier"; @@ -990,6 +1056,9 @@ /* No comment provided by engineer. */ "Created on %@" = "Créé le %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "Création d'un lien d'archive"; + /* No comment provided by engineer. */ "Creating link…" = "Création d'un lien…"; @@ -1137,6 +1206,9 @@ /* No comment provided by engineer. */ "Delete database" = "Supprimer la base de données"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Supprimer la base de données de cet appareil"; + /* server test step */ "Delete file" = "Supprimer le fichier"; @@ -1329,9 +1401,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Rétrograder et ouvrir le chat"; +/* No comment provided by engineer. */ +"Download failed" = "Échec du téléchargement"; + /* server test step */ "Download file" = "Télécharger le fichier"; +/* No comment provided by engineer. */ +"Downloading archive" = "Téléchargement de l'archive"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Téléchargement des détails du lien"; + /* No comment provided by engineer. */ "Duplicate display name!" = "Nom d'affichage en double !"; @@ -1365,6 +1446,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Activer pour tous"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Activé dans les conversations directes (BETA) !"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Activer les notifications instantanées ?"; @@ -1479,6 +1563,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Entrer le code d'accès"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Entrer la phrase secrète"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Entrez la phrase secrète…"; @@ -1518,6 +1605,9 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Erreur lors de l'ajout de membre·s"; +/* No comment provided by engineer. */ +"Error allowing contact PQ encryption" = "Erreur lors de la négociation du chiffrement PQ"; + /* No comment provided by engineer. */ "Error changing address" = "Erreur de changement d'adresse"; @@ -1572,6 +1662,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Erreur lors de la suppression du profil utilisateur"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Erreur lors du téléchargement de l'archive"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "Erreur lors de l'activation des accusés de réception !"; @@ -1617,6 +1710,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Erreur lors de l'enregistrement de la phrase de passe dans la keychain"; +/* when migrating */ +"Error saving settings" = "Erreur lors de l'enregistrement des paramètres"; + /* No comment provided by engineer. */ "Error saving user password" = "Erreur d'enregistrement du mot de passe de l'utilisateur"; @@ -1659,6 +1755,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Erreur de mise à jour de la confidentialité de l'utilisateur"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Erreur lors de l'envoi de l'archive"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Erreur lors de la vérification de la phrase secrète :"; + /* No comment provided by engineer. */ "Error: " = "Erreur : "; @@ -1681,7 +1783,7 @@ "Exit without saving" = "Quitter sans enregistrer"; /* chat item action */ -"Expand" = "Développer"; +"Expand" = "Étendre"; /* No comment provided by engineer. */ "Export database" = "Exporter la base de données"; @@ -1692,6 +1794,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Archive de la base de données exportée."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "Le fichier exporté n'existe pas"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Exportation de l'archive de la base de données…"; @@ -1734,6 +1839,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Filtrer les messages non lus et favoris."; +/* No comment provided by engineer. */ +"Finalize migration" = "Finaliser le transfert"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Finalisez le transfert sur l'autre appareil."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "Enfin, les voilà ! 🚀"; @@ -1917,6 +2028,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "Comment utiliser vos serveurs"; +/* No comment provided by engineer. */ +"Hungarian interface" = "Interface en hongrois"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "Serveurs ICE (un par ligne)"; @@ -1956,6 +2070,12 @@ /* No comment provided by engineer. */ "Import database" = "Importer la base de données"; +/* No comment provided by engineer. */ +"Import failed" = "Échec de l'importation"; + +/* No comment provided by engineer. */ +"Importing archive" = "Importation de l'archive"; + /* No comment provided by engineer. */ "Improved message delivery" = "Amélioration de la transmission des messages"; @@ -1965,6 +2085,9 @@ /* No comment provided by engineer. */ "Improved server configuration" = "Configuration de serveur améliorée"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "Pour continuer, le chat doit être interrompu."; + /* No comment provided by engineer. */ "In reply to" = "En réponse à"; @@ -2049,6 +2172,9 @@ /* No comment provided by engineer. */ "Invalid link" = "Lien invalide"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Confirmation de migration invalide"; + /* No comment provided by engineer. */ "Invalid name!" = "Nom invalide !"; @@ -2313,6 +2439,9 @@ /* No comment provided by engineer. */ "Message text" = "Texte du message"; +/* No comment provided by engineer. */ +"Message too large" = "Message trop volumineux"; + /* No comment provided by engineer. */ "Messages" = "Messages"; @@ -2322,9 +2451,36 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Les messages de %@ seront affichés !"; +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Les messages, fichiers et appels sont protégés par un chiffrement **de bout en bout** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction."; + +/* 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." = "Les messages, fichiers et appels sont protégés par un chiffrement **2e2 résistant post-quantique** avec une confidentialité persistante, une répudiation et une récupération en cas d'effraction."; + +/* No comment provided by engineer. */ +"Migrate device" = "Transférer l'appareil"; + +/* No comment provided by engineer. */ +"Migrate from another device" = "Transférer depuis un autre appareil"; + +/* No comment provided by engineer. */ +"Migrate here" = "Transférer ici"; + +/* No comment provided by engineer. */ +"Migrate to another device" = "Transférer vers un autre appareil"; + +/* No comment provided by engineer. */ +"Migrate to another device via QR code." = "Transférer vers un autre appareil via un code QR."; + +/* No comment provided by engineer. */ +"Migrating" = "Transfert"; + /* No comment provided by engineer. */ "Migrating database archive…" = "Migration de l'archive de la base de données…"; +/* No comment provided by engineer. */ +"Migration complete" = "Transfert terminé"; + /* No comment provided by engineer. */ "Migration error:" = "Erreur de migration :"; @@ -2355,7 +2511,7 @@ /* copied message info */ "Moderated at: %@" = "Modéré à : %@"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "modéré par %@"; /* time unit */ @@ -2582,6 +2738,9 @@ /* No comment provided by engineer. */ "Open group" = "Ouvrir le groupe"; +/* authentication reason */ +"Open migration to another device" = "Ouvrir le transfert vers un autre appareil"; + /* No comment provided by engineer. */ "Open Settings" = "Ouvrir les Paramètres"; @@ -2594,9 +2753,15 @@ /* No comment provided by engineer. */ "Opening app…" = "Ouverture de l'app…"; +/* No comment provided by engineer. */ +"Or paste archive link" = "Ou coller le lien de l'archive"; + /* No comment provided by engineer. */ "Or scan QR code" = "Ou scanner le code QR"; +/* No comment provided by engineer. */ +"Or securely share this file link" = "Ou partagez en toute sécurité le lien de ce fichier"; + /* No comment provided by engineer. */ "Or show this code" = "Ou présenter ce code"; @@ -2648,6 +2813,9 @@ /* message decrypt error item */ "Permanent decryption error" = "Erreur de déchiffrement"; +/* No comment provided by engineer. */ +"Picture-in-picture calls" = "Appels picture-in-picture"; + /* No comment provided by engineer. */ "PING count" = "Nombre de PING"; @@ -2666,6 +2834,9 @@ /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Veuillez vérifier vos préférences ainsi que celles de votre contact."; +/* No comment provided by engineer. */ +"Please confirm that network settings are correct for this device." = "Veuillez confirmer que les paramètres réseau de cet appareil sont corrects."; + /* No comment provided by engineer. */ "Please contact developers.\nError: %@" = "Veuillez contacter les développeurs.\nErreur : %@"; @@ -2699,6 +2870,9 @@ /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Il est possible que l'empreinte du certificat dans l'adresse du serveur soit incorrecte"; +/* No comment provided by engineer. */ +"Post-quantum E2EE" = "E2EE post-quantique"; + /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserver le brouillon du dernier message, avec les pièces jointes."; @@ -2780,6 +2954,15 @@ /* No comment provided by engineer. */ "Push notifications" = "Notifications push"; +/* No comment provided by engineer. */ +"Push server" = "Serveur Push"; + +/* chat item text */ +"quantum resistant e2e encryption" = "chiffrement e2e résistant post-quantique"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "Chiffrement résistant post-quantique"; + /* No comment provided by engineer. */ "Rate the app" = "Évaluer l'app"; @@ -2915,9 +3098,18 @@ /* No comment provided by engineer. */ "Repeat connection request?" = "Répéter la demande de connexion ?"; +/* No comment provided by engineer. */ +"Repeat download" = "Répéter le téléchargement"; + +/* No comment provided by engineer. */ +"Repeat import" = "Répéter l'importation"; + /* No comment provided by engineer. */ "Repeat join request?" = "Répéter la requête d'adhésion ?"; +/* No comment provided by engineer. */ +"Repeat upload" = "Répéter l'envoi"; + /* chat item action */ "Reply" = "Répondre"; @@ -2975,6 +3167,9 @@ /* No comment provided by engineer. */ "Run chat" = "Exécuter le chat"; +/* No comment provided by engineer. */ +"Safer groups" = "Groupes plus sûrs"; + /* chat item action */ "Save" = "Enregistrer"; @@ -3207,14 +3402,17 @@ "Set it instead of system authentication." = "Il permet de remplacer l'authentification du système."; /* profile update event chat item */ -"set new contact address" = "définir une nouvelle adresse de contact"; +"set new contact address" = "a changé d'adresse de contact"; /* profile update event chat item */ -"set new profile picture" = "définir une nouvelle image de profil"; +"set new profile picture" = "a changé d'image de profil"; /* No comment provided by engineer. */ "Set passcode" = "Définir le code d'accès"; +/* No comment provided by engineer. */ +"Set passphrase" = "Définir une phrase secrète"; + /* No comment provided by engineer. */ "Set passphrase to export" = "Définir la phrase secrète pour l'export"; @@ -3258,7 +3456,10 @@ "Show last messages" = "Voir les derniers messages"; /* No comment provided by engineer. */ -"Show preview" = "Montrer l'aperçu"; +"Show preview" = "Afficher l'aperçu"; + +/* No comment provided by engineer. */ +"Show QR code" = "Afficher le code QR"; /* No comment provided by engineer. */ "Show:" = "Afficher :"; @@ -3320,6 +3521,9 @@ /* notification title */ "Somebody" = "Quelqu'un"; +/* chat item text */ +"standard end-to-end encryption" = "chiffrement de bout en bout standard"; + /* No comment provided by engineer. */ "Start chat" = "Démarrer le chat"; @@ -3335,6 +3539,9 @@ /* No comment provided by engineer. */ "Stop" = "Arrêter"; +/* No comment provided by engineer. */ +"Stop chat" = "Arrêter le chat"; + /* No comment provided by engineer. */ "Stop chat to enable database actions" = "Arrêter le chat pour permettre des actions sur la base de données"; @@ -3362,6 +3569,9 @@ /* authentication reason */ "Stop SimpleX" = "Arrêter SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "Arrêt du chat"; + /* No comment provided by engineer. */ "strike" = "barré"; @@ -3512,6 +3722,12 @@ /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Cette action ne peut être annulée - votre profil, vos contacts, vos messages et vos fichiers seront irréversiblement perdus."; +/* E2EE info chat item */ +"This chat is protected by end-to-end encryption." = "Cette discussion est protégée par un chiffrement de bout en bout."; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "Cette discussion est protégée par un chiffrement de bout en bout résistant post-quantique."; + /* notification title */ "this contact" = "ce contact"; @@ -3596,12 +3812,21 @@ /* No comment provided by engineer. */ "Unblock" = "Débloquer"; +/* No comment provided by engineer. */ +"Unblock for all" = "Débloquer pour tous"; + /* No comment provided by engineer. */ "Unblock member" = "Débloquer ce membre"; +/* No comment provided by engineer. */ +"Unblock member for all?" = "Débloquer le membre pour tous ?"; + /* No comment provided by engineer. */ "Unblock member?" = "Débloquer ce membre ?"; +/* rcv group event chat item */ +"unblocked %@" = "%@ débloqué"; + /* item status description */ "Unexpected error: %@" = "Erreur inattendue : %@"; @@ -3695,9 +3920,15 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Mettre à niveau et ouvrir le chat"; +/* No comment provided by engineer. */ +"Upload failed" = "Échec de l'envoi"; + /* server test step */ "Upload file" = "Transférer le fichier"; +/* No comment provided by engineer. */ +"Uploading archive" = "Envoi de l'archive"; + /* No comment provided by engineer. */ "Use .onion hosts" = "Utiliser les hôtes .onions"; @@ -3728,6 +3959,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Utiliser les serveurs SimpleX Chat ?"; +/* No comment provided by engineer. */ +"Use the app while in the call." = "Utiliser l'application pendant l'appel."; + /* No comment provided by engineer. */ "User profile" = "Profil d'utilisateur"; @@ -3735,7 +3969,7 @@ "Using .onion hosts requires compatible VPN provider." = "L'utilisation des hôtes .onion nécessite un fournisseur VPN compatible."; /* No comment provided by engineer. */ -"Using SimpleX Chat servers." = "Utilisation des serveurs SimpleX Chat."; +"Using SimpleX Chat servers." = "Vous utilisez les serveurs SimpleX."; /* No comment provided by engineer. */ "v%@" = "v%@"; @@ -3755,6 +3989,12 @@ /* No comment provided by engineer. */ "Verify connections" = "Vérifier les connexions"; +/* No comment provided by engineer. */ +"Verify database passphrase" = "Vérifier la phrase secrète de la base de données"; + +/* No comment provided by engineer. */ +"Verify passphrase" = "Vérifier la phrase secrète"; + /* No comment provided by engineer. */ "Verify security code" = "Vérifier le code de sécurité"; @@ -3833,6 +4073,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "veut établir une connexion !"; +/* No comment provided by engineer. */ +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Attention : démarrer une session de chat sur plusieurs appareils n'est pas pris en charge et entraînera des dysfonctionnements au niveau de la transmission des messages"; + /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Attention : vous risquez de perdre des données !"; @@ -3848,6 +4091,9 @@ /* No comment provided by engineer. */ "Welcome message" = "Message de bienvenue"; +/* No comment provided by engineer. */ +"Welcome message is too long" = "Le message de bienvenue est trop long"; + /* No comment provided by engineer. */ "What's new" = "Quoi de neuf ?"; @@ -3855,7 +4101,7 @@ "When available" = "Quand disponible"; /* No comment provided by engineer. */ -"When people request to connect, you can accept or reject it." = "Lorsque des personnes demandent à se connecter, vous pouvez les accepter ou les refuser."; +"When people request to connect, you can accept or reject it." = "Vous pouvez accepter ou refuser les demandes de contacts."; /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Lorsque vous partagez un profil incognito avec quelqu'un, ce profil sera utilisé pour les groupes auxquels il vous invite."; @@ -3884,6 +4130,9 @@ /* No comment provided by engineer. */ "You" = "Vous"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "Vous **ne devez pas** utiliser la même base de données sur deux appareils."; + /* No comment provided by engineer. */ "You accepted connection" = "Vous avez accepté la connexion"; @@ -3929,6 +4178,9 @@ /* No comment provided by engineer. */ "you are observer" = "vous êtes observateur"; +/* snd group event chat item */ +"you blocked %@" = "vous avez bloqué %@"; + /* No comment provided by engineer. */ "You can accept calls from lock screen, without device and app authentication." = "Vous pouvez accepter des appels à partir de l'écran de verrouillage, sans authentification de l'appareil ou de l'application."; @@ -3941,6 +4193,9 @@ /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "Vous pouvez les activer ultérieurement via les paramètres de Confidentialité et Sécurité de l'application."; +/* No comment provided by engineer. */ +"You can give another try." = "Vous pouvez faire un nouvel essai."; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "Vous pouvez masquer ou mettre en sourdine un profil d'utilisateur - faites-le glisser vers la droite."; @@ -3960,7 +4215,7 @@ "You can share this address with your contacts to let them connect with **%@**." = "Vous pouvez partager cette adresse avec vos contacts pour leur permettre de se connecter avec **%@**."; /* No comment provided by engineer. */ -"You can share your address as a link or QR code - anybody can connect to you." = "Vous pouvez partager votre adresse sous forme de lien ou de code QR - n'importe qui pourra se connecter à vous."; +"You can share your address as a link or QR code - anybody can connect to you." = "Vous pouvez partager votre adresse sous la forme d'un lien ou d'un code QR - tout le monde peut l'utiliser pour vous contacter."; /* No comment provided by engineer. */ "You can start chat via app Settings / Database or by restarting the app" = "Vous pouvez lancer le chat via Paramètres / Base de données ou en redémarrant l'app"; @@ -4040,6 +4295,9 @@ /* chat list item description */ "you shared one-time link incognito" = "vous avez partagé un lien unique en incognito"; +/* snd group event chat item */ +"you unblocked %@" = "vous avez débloqué %@"; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "Vous serez connecté·e au groupe lorsque l'appareil de l'hôte sera en ligne, veuillez attendre ou vérifier plus tard !"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings new file mode 100644 index 0000000000..f9a470fbbc --- /dev/null +++ b/apps/ios/hu.lproj/Localizable.strings @@ -0,0 +1,4186 @@ +/* No comment provided by engineer. */ +"\n" = "\n"; + +/* No comment provided by engineer. */ +" " = " "; + +/* No comment provided by engineer. */ +" " = " "; + +/* No comment provided by engineer. */ +" " = " "; + +/* No comment provided by engineer. */ +" (" = " ("; + +/* No comment provided by engineer. */ +" (can be copied)" = " (másolható)"; + +/* No comment provided by engineer. */ +"_italic_" = "\\_dőlt_"; + +/* No comment provided by engineer. */ +"- connect to [directory service](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion) (BETA)!\n- delivery receipts (up to 20 members).\n- faster and more stable." = "- kapcsolódás a [könyvtár szolgáltatáshoz] (simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2Ld3%3DWpxkKFeXSPv3pwp %2F%3Fv%3D1-2%26dh %3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6glco6bqjETA)4Beklco6bqj)\n- kézbesítési jelentések (legfeljebb 20 tag).\n- gyorsabb és stabilabb."; + +/* No comment provided by engineer. */ +"- more stable message delivery.\n- a bit better groups.\n- and more!" = "- stabilabb üzenetkézbesítés.\n- valamivel jobb csoportok.\n- és még sok más!"; + +/* No comment provided by engineer. */ +"- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- opcionális értesítés a törölt kapcsolatokról.\n- profilnevek szóközökkel.\n- és még sok más!"; + +/* No comment provided by engineer. */ +"- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- hangüzenetek legfeljebb 5 perces időtartamig.\n- egyedi eltűnési időhatár megadása.\n- előzmények szerkesztése."; + +/* No comment provided by engineer. */ +", " = ", "; + +/* No comment provided by engineer. */ +": " = ": "; + +/* No comment provided by engineer. */ +"!1 colored!" = "!1 színezett!"; + +/* No comment provided by engineer. */ +"." = "."; + +/* No comment provided by engineer. */ +"(" = "("; + +/* No comment provided by engineer. */ +"(new)" = "(új)"; + +/* No comment provided by engineer. */ +"(this device v%@)" = "(ez az eszköz v%@)"; + +/* No comment provided by engineer. */ +")" = ")"; + +/* No comment provided by engineer. */ +"[Contribute](https://github.com/simplex-chat/simplex-chat#contribute)" = "[Hozzájárulás](https://github.com/simplex-chat/simplex-chat#contribute)"; + +/* No comment provided by engineer. */ +"[Send us email](mailto:chat@simplex.chat)" = "[Küldjön nekünk e-mailt](mailto:chat@simplex.chat)"; + +/* No comment provided by engineer. */ +"[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Csillag a GitHubon](https://github.com/simplex-chat/simplex-chat)"; + +/* No comment provided by engineer. */ +"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Ismerős hozzáadása**: új meghívó hivatkozás létrehozásához, vagy egy kapott hivatkozáson keresztül történő csatlakozáshoz."; + +/* No comment provided by engineer. */ +"**Add new contact**: to create your one-time QR Code for your contact." = "**Új ismerős hozzáadása**: egyszer használatos QR-kód vagy hivatkozás létrehozása a kapcsolattartóhoz."; + +/* No comment provided by engineer. */ +"**Create group**: to create a new group." = "**Csoport létrehozása**: új csoport létrehozásához."; + +/* No comment provided by engineer. */ +"**e2e encrypted** audio call" = "**e2e titkosított** hanghívás"; + +/* No comment provided by engineer. */ +"**e2e encrypted** video call" = "**e2e titkosított** videóhívás"; + +/* No comment provided by engineer. */ +"**More private**: check new messages every 20 minutes. Device token is shared with SimpleX Chat server, but not how many contacts or messages you have." = "**Privátabb**: 20 percenként ellenőrzi az új üzeneteket. Az eszköztoken megosztásra kerül a SimpleX Chat kiszolgálóval, de az nem, hogy hány ismerőse vagy üzenete van."; + +/* No comment provided by engineer. */ +"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Legprivátabb**: ne használja a SimpleX Chat értesítési szervert, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást)."; + +/* No comment provided by engineer. */ +"**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Figyelem**: NEM tudja visszaállítani vagy megváltoztatni jelmondatát, ha elveszíti azt."; + +/* No comment provided by engineer. */ +"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Javasolt**: az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési szerverre, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik."; + +/* No comment provided by engineer. */ +"**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Figyelmeztetés**: Az azonnali push-értesítésekhez a kulcstárolóban tárolt jelmondat megadása szükséges."; + +/* No comment provided by engineer. */ +"*bold*" = "\\*félkövér*"; + +/* copied message info title, # */ +"# %@" = "# %@"; + +/* copied message info */ +"## History" = "## Előzmények"; + +/* copied message info */ +"## In reply to" = "## Válaszul erre"; + +/* No comment provided by engineer. */ +"#secret#" = "#titkos#"; + +/* No comment provided by engineer. */ +"%@" = "%@"; + +/* No comment provided by engineer. */ +"%@ (current)" = "%@ (jelenlegi)"; + +/* copied message info */ +"%@ (current):" = "%@ (jelenlegi):"; + +/* No comment provided by engineer. */ +"%@ / %@" = "%@ / %@"; + +/* No comment provided by engineer. */ +"%@ %@" = "%@ %@"; + +/* No comment provided by engineer. */ +"%@ and %@" = "%@ és %@"; + +/* No comment provided by engineer. */ +"%@ and %@ connected" = "%@ és %@ csatlakozott"; + +/* copied message info, <sender> at <time> */ +"%@ at %@:" = "%1$@ %2$@-kor:"; + +/* No comment provided by engineer. */ +"%@ connected" = "%@ csatlakozott"; + +/* notification title */ +"%@ is connected!" = "%@ csatlakozott!"; + +/* No comment provided by engineer. */ +"%@ is not verified" = "%@ nem ellenőrzött"; + +/* No comment provided by engineer. */ +"%@ is verified" = "%@ ellenőrizve"; + +/* No comment provided by engineer. */ +"%@ servers" = "%@ kiszolgáló"; + +/* notification title */ +"%@ wants to connect!" = "%@ csatlakozni szeretne!"; + +/* No comment provided by engineer. */ +"%@, %@ and %lld members" = "%@, %@ és további %lld tag"; + +/* No comment provided by engineer. */ +"%@, %@ and %lld other members connected" = "%@, %@ és további %lld tag csatlakozott"; + +/* copied message info */ +"%@:" = "%@:"; + +/* time interval */ +"%d days" = "%d nap"; + +/* time interval */ +"%d hours" = "%d óra"; + +/* time interval */ +"%d min" = "%d perc"; + +/* time interval */ +"%d months" = "%d hónap"; + +/* time interval */ +"%d sec" = "%d mp"; + +/* integrity error chat item */ +"%d skipped message(s)" = "%d kihagyott üzenet"; + +/* time interval */ +"%d weeks" = "%d hét"; + +/* No comment provided by engineer. */ +"%lld" = "%lld"; + +/* No comment provided by engineer. */ +"%lld %@" = "%lld %@"; + +/* No comment provided by engineer. */ +"%lld contact(s) selected" = "%lld ismerős kiválasztva"; + +/* No comment provided by engineer. */ +"%lld file(s) with total size of %@" = "%lld fájl, amely(ek)nek teljes mérete: %@"; + +/* No comment provided by engineer. */ +"%lld group events" = "%lld csoportesemény"; + +/* No comment provided by engineer. */ +"%lld members" = "%lld tag"; + +/* No comment provided by engineer. */ +"%lld messages blocked" = "%lld üzenet blokkolva"; + +/* No comment provided by engineer. */ +"%lld messages blocked by admin" = "%lld üzenet blokkolva az admin által"; + +/* No comment provided by engineer. */ +"%lld messages marked deleted" = "%lld törlésre megjelölt üzenet"; + +/* No comment provided by engineer. */ +"%lld messages moderated by %@" = "%@ %lld üzenetet moderált"; + +/* No comment provided by engineer. */ +"%lld minutes" = "%lld perc"; + +/* No comment provided by engineer. */ +"%lld new interface languages" = "%lld új nyelvi csomag"; + +/* No comment provided by engineer. */ +"%lld second(s)" = "%lld másodperc"; + +/* No comment provided by engineer. */ +"%lld seconds" = "%lld másodperc"; + +/* No comment provided by engineer. */ +"%lldd" = "%lldd"; + +/* No comment provided by engineer. */ +"%lldh" = "%lldh"; + +/* No comment provided by engineer. */ +"%lldk" = "%lldk"; + +/* No comment provided by engineer. */ +"%lldm" = "%lldm"; + +/* No comment provided by engineer. */ +"%lldmth" = "%lldmth"; + +/* No comment provided by engineer. */ +"%llds" = "%llds"; + +/* No comment provided by engineer. */ +"%lldw" = "%lldw"; + +/* No comment provided by engineer. */ +"%u messages failed to decrypt." = "%u üzenet visszafejtése sikertelen."; + +/* No comment provided by engineer. */ +"%u messages skipped." = "%u kihagyott üzenet."; + +/* No comment provided by engineer. */ +"`a + b`" = "a + b"; + +/* email text */ +"<p>Hi!</p>\n<p><a href=\"%@\">Connect to me via SimpleX Chat</a></p>" = "<p>Üdvözlöm!</p>\n<p><a href=\"%@\">Csatlakozzon hozzám a SimpleX Chaten</a></p>"; + +/* No comment provided by engineer. */ +"~strike~" = "\\~áthúzott~"; + +/* time to disappear */ +"0 sec" = "0 mp"; + +/* No comment provided by engineer. */ +"0s" = "0s"; + +/* time interval */ +"1 day" = "1 nap"; + +/* time interval */ +"1 hour" = "1 óra"; + +/* No comment provided by engineer. */ +"1 minute" = "1 perc"; + +/* time interval */ +"1 month" = "1 hónap"; + +/* time interval */ +"1 week" = "1 hét"; + +/* No comment provided by engineer. */ +"5 minutes" = "5 perc"; + +/* No comment provided by engineer. */ +"6" = "6"; + +/* No comment provided by engineer. */ +"30 seconds" = "30 másodperc"; + +/* No comment provided by engineer. */ +"A few more things" = "Még néhány dolog"; + +/* notification title */ +"A new contact" = "Egy új ismerős"; + +/* No comment provided by engineer. */ +"A new random profile will be shared." = "Egy új, véletlenszerű profil kerül megosztásra."; + +/* No comment provided by engineer. */ +"A separate TCP connection will be used **for each chat profile you have in the app**." = "A rendszer külön TCP-kapcsolatot fog használni **az alkalmazásban található minden csevegési profilhoz**."; + +/* No comment provided by engineer. */ +"A separate TCP connection will be used **for each contact and group member**.\n**Please note**: if you have many connections, your battery and traffic consumption can be substantially higher and some connections may fail." = "A rendszer külön TCP-kapcsolatot fog használni **minden ismerőshöz és csoporttaghoz**.\n**Figyelem**: sok kapcsolódás esetén, az akkumulátor- és adatforgalom fogyasztás jelentősen megnőhet, és egyes kapcsolatok meghiúsulhatnak."; + +/* No comment provided by engineer. */ +"Abort" = "Megszakítás"; + +/* No comment provided by engineer. */ +"Abort changing address" = "Címváltoztatás megszakítása"; + +/* No comment provided by engineer. */ +"Abort changing address?" = "Címváltoztatás megszakítása??"; + +/* No comment provided by engineer. */ +"About SimpleX" = "A SimpleX névjegye"; + +/* No comment provided by engineer. */ +"About SimpleX address" = "A SimpleX azonosítóról"; + +/* No comment provided by engineer. */ +"About SimpleX Chat" = "A SimpleX Chat névjegye"; + +/* No comment provided by engineer. */ +"above, then choose:" = "fent, majd válassza ki:"; + +/* No comment provided by engineer. */ +"Accent color" = "Kiemelő szín"; + +/* accept contact request via notification + accept incoming call via notification */ +"Accept" = "Elfogadás"; + +/* No comment provided by engineer. */ +"Accept connection request?" = "Kapcsolatfelvétel elfogadása?"; + +/* notification body */ +"Accept contact request from %@?" = "Elfogadja %@ kapcsolat kérését?"; + +/* accept contact request via notification */ +"Accept incognito" = "Fogadás inkognítóban"; + +/* call status */ +"accepted call" = "elfogadott hívás"; + +/* No comment provided by engineer. */ +"Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Azonosító hozzáadása a profilhoz, hogy az ismerősök megoszthassák másokkal. A profilfrissítés elküldésre kerül ismerősők számára."; + +/* No comment provided by engineer. */ +"Add contact" = "Ismerős hozzáadása"; + +/* No comment provided by engineer. */ +"Add preset servers" = "Előre beállított kiszolgálók hozzáadása"; + +/* No comment provided by engineer. */ +"Add profile" = "Profil hozzáadása"; + +/* No comment provided by engineer. */ +"Add server…" = "Kiszolgáló hozzáadása…"; + +/* No comment provided by engineer. */ +"Add servers by scanning QR codes." = "Kiszolgáló hozzáadása QR-kód beolvasásával."; + +/* No comment provided by engineer. */ +"Add to another device" = "Hozzáadás egy másik eszközhöz"; + +/* No comment provided by engineer. */ +"Add welcome message" = "Üdvözlő üzenet hozzáadása"; + +/* No comment provided by engineer. */ +"Address" = "Cím"; + +/* No comment provided by engineer. */ +"Address change will be aborted. Old receiving address will be used." = "A cím módosítása megszakad. A régi fogadási cím kerül felhasználásra."; + +/* member role */ +"admin" = "admin"; + +/* No comment provided by engineer. */ +"Admins can create the links to join groups." = "Az adminok hivatkozásokat hozhatnak létre a csoportokhoz való csatlakozáshoz."; + +/* No comment provided by engineer. */ +"Advanced network settings" = "Speciális hálózati beállítások"; + +/* chat item text */ +"agreeing encryption for %@…" = "titkosítás jóváhagyása %@ számára…"; + +/* chat item text */ +"agreeing encryption…" = "titkosítás elfogadása…"; + +/* No comment provided by engineer. */ +"All app data is deleted." = "Minden alkalmazásadat törölve."; + +/* No comment provided by engineer. */ +"All chats and messages will be deleted - this cannot be undone!" = "Minden csevegés és üzenet törlésre kerül - ez nem vonható vissza!"; + +/* No comment provided by engineer. */ +"All data is erased when it is entered." = "A jelkód megadása után minden adat törlésre kerül."; + +/* No comment provided by engineer. */ +"All group members will remain connected." = "Minden csoporttag csatlakoztatva marad."; + +/* No comment provided by engineer. */ +"All messages will be deleted - this cannot be undone!" = "Minden üzenet törlésre kerül – ez nem vonható vissza!"; + +/* No comment provided by engineer. */ +"All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Minden üzenet törlésre kerül - ezt nem vonható vissza! Az üzenetek CSAK az ön számára törlődnek."; + +/* No comment provided by engineer. */ +"All new messages from %@ will be hidden!" = "Minden új üzenet elrejtésre kerül tőle: %@!"; + +/* No comment provided by engineer. */ +"All your contacts will remain connected." = "Minden ismerős csatlakoztatva marad."; + +/* No comment provided by engineer. */ +"All your contacts will remain connected. Profile update will be sent to your contacts." = "Ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél."; + +/* No comment provided by engineer. */ +"Allow" = "Engedélyezés"; + +/* No comment provided by engineer. */ +"Allow calls only if your contact allows them." = "Hívások engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi."; + +/* No comment provided by engineer. */ +"Allow disappearing messages only if your contact allows it to you." = "Eltűnő üzenetek engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi az ön számára."; + +/* No comment provided by engineer. */ +"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Üzenet végleges törlésének engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi. (24 óra)"; + +/* No comment provided by engineer. */ +"Allow message reactions only if your contact allows them." = "Üzenetreakciók engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi."; + +/* No comment provided by engineer. */ +"Allow message reactions." = "Üzenetreakciók engedélyezése."; + +/* No comment provided by engineer. */ +"Allow sending direct messages to members." = "Közvetlen üzenetek küldésének engedélyezése tagok részére."; + +/* No comment provided by engineer. */ +"Allow sending disappearing messages." = "Eltűnő üzenetek küldésének engedélyezése."; + +/* No comment provided by engineer. */ +"Allow to irreversibly delete sent messages. (24 hours)" = "Elküldött üzenetek visszafordíthatatlan törlésének engedélyezése. (24 óra)"; + +/* No comment provided by engineer. */ +"Allow to send files and media." = "Fájlok és médiatartalom küldésének engedélyezése."; + +/* No comment provided by engineer. */ +"Allow to send voice messages." = "Hangüzenetek küldésének engedélyezése."; + +/* No comment provided by engineer. */ +"Allow voice messages only if your contact allows them." = "Hangüzenetek küldésének engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi."; + +/* No comment provided by engineer. */ +"Allow voice messages?" = "Hangüzenetek engedélyezése?"; + +/* No comment provided by engineer. */ +"Allow your contacts adding message reactions." = "Ismerősök általi üzenetreakciók hozzáadásának engedélyezése."; + +/* No comment provided by engineer. */ +"Allow your contacts to call you." = "Hívások engedélyezése ismerősök számára."; + +/* No comment provided by engineer. */ +"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "Elküldött üzenetek visszafordíthatatlan törlésének engedélyezése ismerősök számára. (24 óra)"; + +/* No comment provided by engineer. */ +"Allow your contacts to send disappearing messages." = "Eltűnő üzenetek engedélyezése ismerősök számára."; + +/* No comment provided by engineer. */ +"Allow your contacts to send voice messages." = "Hangüzenetek küldésének engedélyezése ismerősök számára."; + +/* No comment provided by engineer. */ +"Already connected?" = "Csatlakoztatva?"; + +/* No comment provided by engineer. */ +"Already connecting!" = "Kapcsolódás folyamatban!"; + +/* No comment provided by engineer. */ +"Already joining the group!" = "Csatlakozás folyamatban!"; + +/* pref value */ +"always" = "mindig"; + +/* No comment provided by engineer. */ +"Always use relay" = "Mindig használjon átjátszó kiszolgálót"; + +/* No comment provided by engineer. */ +"An empty chat profile with the provided name is created, and the app opens as usual." = "Egy üres csevegési profil jön létre a megadott névvel, és az alkalmazás a szokásos módon megnyílik."; + +/* No comment provided by engineer. */ +"and %lld other events" = "és %lld egyéb esemény"; + +/* No comment provided by engineer. */ +"Answer call" = "Hívás fogadása"; + +/* No comment provided by engineer. */ +"App build: %@" = "Az alkalmazás build száma: %@"; + +/* No comment provided by engineer. */ +"App encrypts new local files (except videos)." = "Az alkalmazás titkosítja a helyi fájlokat (a videók kivételével)."; + +/* No comment provided by engineer. */ +"App icon" = "Alkalmazás ikon"; + +/* No comment provided by engineer. */ +"App passcode" = "Alkalmazás jelkód"; + +/* No comment provided by engineer. */ +"App passcode is replaced with self-destruct passcode." = "Az alkalmazás jelkód helyettesítésre kerül egy önmegsemmisítő jelkóddal."; + +/* No comment provided by engineer. */ +"App version" = "Alkalmazás verzió"; + +/* No comment provided by engineer. */ +"App version: v%@" = "Alkalmazás verzió: v%@"; + +/* No comment provided by engineer. */ +"Appearance" = "Megjelenés"; + +/* No comment provided by engineer. */ +"Attach" = "Csatolás"; + +/* No comment provided by engineer. */ +"Audio & video calls" = "Hang- és videóhívások"; + +/* No comment provided by engineer. */ +"Audio and video calls" = "Hang- és videóhívások"; + +/* No comment provided by engineer. */ +"audio call (not e2e encrypted)" = "hanghívás (nem e2e titkosított)"; + +/* chat feature */ +"Audio/video calls" = "Hang-/videóhívások"; + +/* No comment provided by engineer. */ +"Audio/video calls are prohibited." = "A hang- és videóhívások le vannak tiltva."; + +/* PIN entry */ +"Authentication cancelled" = "Hitelesítés megszakítva"; + +/* No comment provided by engineer. */ +"Authentication failed" = "Hitelesítés sikertelen"; + +/* No comment provided by engineer. */ +"Authentication is required before the call is connected, but you may miss calls." = "A hívás csatlakoztatása előtt hitelesítésre van szükség, de előfordulhat, hogy nem tud hívásokat fogadni."; + +/* No comment provided by engineer. */ +"Authentication unavailable" = "Hitelesítés elérhetetlen"; + +/* member role */ +"author" = "szerző"; + +/* No comment provided by engineer. */ +"Auto-accept" = "Automatikus elfogadás"; + +/* No comment provided by engineer. */ +"Auto-accept contact requests" = "Ismerős jelölések automatikus elfogadása"; + +/* No comment provided by engineer. */ +"Auto-accept images" = "Fotók automatikus elfogadása"; + +/* No comment provided by engineer. */ +"Back" = "Vissza"; + +/* No comment provided by engineer. */ +"Bad desktop address" = "Hibás számítógép azonosító"; + +/* integrity error chat item */ +"bad message hash" = "téves üzenet hash"; + +/* No comment provided by engineer. */ +"Bad message hash" = "Téves üzenet hash"; + +/* integrity error chat item */ +"bad message ID" = "téves üzenet ID"; + +/* No comment provided by engineer. */ +"Bad message ID" = "Téves üzenet ID"; + +/* No comment provided by engineer. */ +"Better groups" = "Javított csoportok"; + +/* No comment provided by engineer. */ +"Better messages" = "Jobb üzenetek"; + +/* No comment provided by engineer. */ +"Block" = "Blokkolás"; + +/* No comment provided by engineer. */ +"Block for all" = "Mindenki számára letiltva"; + +/* No comment provided by engineer. */ +"Block group members" = "Csoporttagok blokkolása"; + +/* No comment provided by engineer. */ +"Block member" = "Tag blokkolása"; + +/* No comment provided by engineer. */ +"Block member for all?" = "Tag letiltása mindenki számára?"; + +/* No comment provided by engineer. */ +"Block member?" = "Tag blokkolása?"; + +/* marked deleted chat item preview text */ +"blocked" = "blokkolva"; + +/* rcv group event chat item */ +"blocked %@" = "%@ letiltva"; + +/* marked deleted chat item preview text */ +"blocked by admin" = "letiltva az admin által"; + +/* No comment provided by engineer. */ +"Blocked by admin" = "Letiltva az admin által"; + +/* No comment provided by engineer. */ +"bold" = "félkövér"; + +/* No comment provided by engineer. */ +"Both you and your contact can add message reactions." = "Mindkét fél is hozzáadhat üzenetreakciókat."; + +/* No comment provided by engineer. */ +"Both you and your contact can irreversibly delete sent messages. (24 hours)" = "Mindkét fél visszafordíthatatlanul törölheti az elküldött üzeneteket. (24 óra)"; + +/* No comment provided by engineer. */ +"Both you and your contact can make calls." = "Mindkét fél tud hívásokat indítani."; + +/* No comment provided by engineer. */ +"Both you and your contact can send disappearing messages." = "Mindkét fél küldhet eltűnő üzeneteket."; + +/* No comment provided by engineer. */ +"Both you and your contact can send voice messages." = "Mindkét fél küldhet hangüzeneteket."; + +/* No comment provided by engineer. */ +"Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Bolgár, finn, thai és ukrán – köszönet a felhasználóknak és a [Weblate-nek](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; + +/* No comment provided by engineer. */ +"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Csevegési profil (alapértelmezett) vagy [kapcsolat alapján] (https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BÉTA)."; + +/* No comment provided by engineer. */ +"Call already ended!" = "A hívás már befejeződött!"; + +/* call status */ +"call error" = "hiba a hívásban"; + +/* call status */ +"call in progress" = "hívás folyamatban"; + +/* call status */ +"calling…" = "hívás…"; + +/* No comment provided by engineer. */ +"Calls" = "Hívások"; + +/* No comment provided by engineer. */ +"Camera not available" = "A fényképező nem elérhető"; + +/* No comment provided by engineer. */ +"Can't invite contact!" = "Ismerősök meghívása le van tiltva!"; + +/* No comment provided by engineer. */ +"Can't invite contacts!" = "Ismerősök meghívása nem lehetséges!"; + +/* No comment provided by engineer. */ +"Cancel" = "Megszakítás"; + +/* feature offered item */ +"cancelled %@" = "%@ törölve"; + +/* No comment provided by engineer. */ +"Cannot access keychain to save database password" = "Nem lehet hozzáférni a kulcstartóhoz az adatbázis jelszavának mentéséhez"; + +/* No comment provided by engineer. */ +"Cannot receive file" = "Nem lehet fogadni a fájlt"; + +/* No comment provided by engineer. */ +"Change" = "Változtatás"; + +/* No comment provided by engineer. */ +"Change database passphrase?" = "Adatbázis jelmondat megváltoztatása?"; + +/* authentication reason */ +"Change lock mode" = "Zárolási mód megváltoztatása"; + +/* No comment provided by engineer. */ +"Change member role?" = "Tag szerepkörének megváltoztatása?"; + +/* authentication reason */ +"Change passcode" = "Jelkód megváltoztatása"; + +/* No comment provided by engineer. */ +"Change receiving address" = "A fogadó cím megváltoztatása"; + +/* No comment provided by engineer. */ +"Change receiving address?" = "Megváltoztatja a fogadó címet?"; + +/* No comment provided by engineer. */ +"Change role" = "Szerepkör megváltoztatása"; + +/* authentication reason */ +"Change self-destruct mode" = "Önmegsemmisítő mód megváltoztatása"; + +/* authentication reason + set passcode view */ +"Change self-destruct passcode" = "Önmegsemmisító jelkód megváltoztatása"; + +/* chat item text */ +"changed address for you" = "Cím megváltoztatva"; + +/* rcv group event chat item */ +"changed role of %@ to %@" = "%1$@ szerepköre megváltozott erre: %2$@"; + +/* rcv group event chat item */ +"changed your role to %@" = "megváltoztatta a szerepkörét erre: %@"; + +/* chat item text */ +"changing address for %@…" = "cím módosítása %@ számára…"; + +/* chat item text */ +"changing address…" = "azonosító megváltoztatása…"; + +/* No comment provided by engineer. */ +"Chat archive" = "Csevegési archívum"; + +/* No comment provided by engineer. */ +"Chat console" = "Csevegési konzol"; + +/* No comment provided by engineer. */ +"Chat database" = "Csevegési adatbázis"; + +/* No comment provided by engineer. */ +"Chat database deleted" = "Csevegési adatbázis törölve"; + +/* No comment provided by engineer. */ +"Chat database imported" = "Csevegési adatbázis importálva"; + +/* No comment provided by engineer. */ +"Chat is running" = "A csevegés fut"; + +/* No comment provided by engineer. */ +"Chat is stopped" = "A csevegés leállt"; + +/* No comment provided by engineer. */ +"Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "A csevegés leállt. Ha már használta ezt az adatbázist egy másik eszközön, úgy visszaállítás szükséges a csevegés megkezdése előtt."; + +/* No comment provided by engineer. */ +"Chat preferences" = "Csevegési beállítások"; + +/* No comment provided by engineer. */ +"Chats" = "Csevegések"; + +/* No comment provided by engineer. */ +"Check server address and try again." = "Kiszolgáló címének ellenőrzése és újrapróbálkozás."; + +/* No comment provided by engineer. */ +"Chinese and Spanish interface" = "Kínai és spanyol kezelőfelület"; + +/* No comment provided by engineer. */ +"Choose file" = "Fájl kiválasztása"; + +/* No comment provided by engineer. */ +"Choose from library" = "Választás a könyvtárból"; + +/* No comment provided by engineer. */ +"Clear" = "Kiürítés"; + +/* No comment provided by engineer. */ +"Clear conversation" = "Beszélgetés kiürítése"; + +/* No comment provided by engineer. */ +"Clear conversation?" = "Beszélgetés kiürítése?"; + +/* No comment provided by engineer. */ +"Clear private notes?" = "Privát jegyzetek törlése?"; + +/* No comment provided by engineer. */ +"Clear verification" = "Hitelesítés törlése"; + +/* No comment provided by engineer. */ +"colored" = "színes"; + +/* No comment provided by engineer. */ +"Colors" = "Színek"; + +/* server test step */ +"Compare file" = "Fájl összehasonlítás"; + +/* No comment provided by engineer. */ +"Compare security codes with your contacts." = "Biztonsági kódok összehasonlítása az ismerősökkel."; + +/* No comment provided by engineer. */ +"complete" = "befejezett"; + +/* No comment provided by engineer. */ +"Configure ICE servers" = "ICE kiszolgálók beállítása"; + +/* No comment provided by engineer. */ +"Confirm" = "Megerősítés"; + +/* No comment provided by engineer. */ +"Confirm database upgrades" = "Adatbázis frissítés megerősítése"; + +/* No comment provided by engineer. */ +"Confirm new passphrase…" = "Új jelmondat megerősítése…"; + +/* No comment provided by engineer. */ +"Confirm Passcode" = "Jelkód megerősítése"; + +/* No comment provided by engineer. */ +"Confirm password" = "Jelszó megerősítése"; + +/* server test step */ +"Connect" = "Kapcsolódás"; + +/* No comment provided by engineer. */ +"Connect automatically" = "Kapcsolódás automatikusan"; + +/* No comment provided by engineer. */ +"Connect incognito" = "Inkognítóban csatlakozva"; + +/* No comment provided by engineer. */ +"Connect to desktop" = "Kapcsolódás számítógéphez"; + +/* No comment provided by engineer. */ +"connect to SimpleX Chat developers." = "Csatlakozás a SimpleX Chat fejlesztőkhöz."; + +/* No comment provided by engineer. */ +"Connect to yourself?" = "Kapcsolódás saját magához?"; + +/* No comment provided by engineer. */ +"Connect to yourself?\nThis is your own one-time link!" = "Kapcsolódás saját magához?\nEz az egyszer használatos hivatkozása!"; + +/* No comment provided by engineer. */ +"Connect to yourself?\nThis is your own SimpleX address!" = "Kapcsolódás saját magához?\nEz a SimpleX azonosítója!"; + +/* No comment provided by engineer. */ +"Connect via contact address" = "Kapcsolódás ismerős azonosítója által"; + +/* No comment provided by engineer. */ +"Connect via link" = "Kapcsolódás egy hivatkozáson keresztül"; + +/* No comment provided by engineer. */ +"Connect via one-time link" = "Kapcsolódás egyszer használatos hivatkozáson keresztül"; + +/* No comment provided by engineer. */ +"Connect with %@" = "Kapcsolódás ezzel: %@"; + +/* No comment provided by engineer. */ +"connected" = "kapcsolódva"; + +/* No comment provided by engineer. */ +"Connected desktop" = "Csatlakoztatott számítógép"; + +/* rcv group event chat item */ +"connected directly" = "közvetlenül kapcsolódva"; + +/* No comment provided by engineer. */ +"Connected to desktop" = "Csatlakozva a számítógéphez"; + +/* No comment provided by engineer. */ +"connecting" = "kapcsolódás"; + +/* No comment provided by engineer. */ +"connecting (accepted)" = "kapcsolódás (elfogadva)"; + +/* No comment provided by engineer. */ +"connecting (announced)" = "kapcsolódás (bejelentve)"; + +/* No comment provided by engineer. */ +"connecting (introduced)" = "kapcsolódás (bejelentve)"; + +/* No comment provided by engineer. */ +"connecting (introduction invitation)" = "csatlakozás (bemutatkozás meghívás)"; + +/* call status */ +"connecting call" = "hívás kapcsolódik…"; + +/* No comment provided by engineer. */ +"Connecting server…" = "Kapcsolódás a kiszolgálóhoz…"; + +/* No comment provided by engineer. */ +"Connecting server… (error: %@)" = "Kapcsolódás a kiszolgálóhoz... (hiba: %@)"; + +/* No comment provided by engineer. */ +"Connecting to desktop" = "Kapcsolódás a számítógéphez"; + +/* chat list item title */ +"connecting…" = "kapcsolódás…"; + +/* No comment provided by engineer. */ +"Connection" = "Kapcsolat"; + +/* No comment provided by engineer. */ +"Connection error" = "Kapcsolódási hiba"; + +/* No comment provided by engineer. */ +"Connection error (AUTH)" = "Kapcsolódási hiba (AUTH)"; + +/* chat list item title (it should not be shown */ +"connection established" = "Kapcsolat létrehozva"; + +/* No comment provided by engineer. */ +"Connection request sent!" = "Kapcsolódási kérés elküldve!"; + +/* No comment provided by engineer. */ +"Connection terminated" = "Kapcsolat megszakítva"; + +/* No comment provided by engineer. */ +"Connection timeout" = "Kapcsolat időtúllépés"; + +/* connection information */ +"connection:%@" = "kapcsolat: %@"; + +/* profile update event chat item */ +"contact %@ changed to %@" = "%1$@ ismerősének neve megváltozott erre: %2$@"; + +/* No comment provided by engineer. */ +"Contact allows" = "Ismerős engedélyezi"; + +/* No comment provided by engineer. */ +"Contact already exists" = "Létező ismerős"; + +/* No comment provided by engineer. */ +"contact has e2e encryption" = "az ismerősnél az e2e titkosítás elérhető"; + +/* No comment provided by engineer. */ +"contact has no e2e encryption" = "az ismerősnél az e2e titkosítás nem elérhető"; + +/* notification */ +"Contact hidden:" = "Ismerős elrejtve:"; + +/* notification */ +"Contact is connected" = "Ismerős csatlakozott"; + +/* No comment provided by engineer. */ +"Contact is not connected yet!" = "Az ismerős még nem csatlakozott!"; + +/* No comment provided by engineer. */ +"Contact name" = "Ismerős neve"; + +/* No comment provided by engineer. */ +"Contact preferences" = "Ismerős beállításai"; + +/* No comment provided by engineer. */ +"Contacts" = "Ismerősök"; + +/* No comment provided by engineer. */ +"Contacts can mark messages for deletion; you will be able to view them." = "Az ismerősök törlésre jelölhetnek üzeneteket ; megtekintheti őket."; + +/* No comment provided by engineer. */ +"Continue" = "Folytatás"; + +/* chat item action */ +"Copy" = "Másolás"; + +/* No comment provided by engineer. */ +"Core version: v%@" = "Alapverziószám: v%@"; + +/* No comment provided by engineer. */ +"Correct name to %@?" = "Név javítása erre: %@?"; + +/* No comment provided by engineer. */ +"Create" = "Létrehozás"; + +/* No comment provided by engineer. */ +"Create a group using a random profile." = "Csoport létrehozása véletlenszerűen létrehozott profillal."; + +/* No comment provided by engineer. */ +"Create an address to let people connect with you." = "Azonosító létrehozása, hogy az emberek kapcsolatba léphessenek önnel."; + +/* server test step */ +"Create file" = "Fájl létrehozása"; + +/* No comment provided by engineer. */ +"Create group" = "Csoport létrehozása"; + +/* No comment provided by engineer. */ +"Create group link" = "Csoportos hivatkozás létrehozása"; + +/* No comment provided by engineer. */ +"Create link" = "Hivatkozás létrehozása"; + +/* No comment provided by engineer. */ +"Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "Új profil létrehozása az [asztali kliensben](https://simplex.chat/downloads/). 💻"; + +/* No comment provided by engineer. */ +"Create profile" = "Profil létrehozása"; + +/* server test step */ +"Create queue" = "Várólista létrehozása"; + +/* No comment provided by engineer. */ +"Create secret group" = "Titkos csoport létrehozása"; + +/* No comment provided by engineer. */ +"Create SimpleX address" = "SimpleX azonosító létrehozása"; + +/* No comment provided by engineer. */ +"Create your profile" = "Saját profil létrehozása"; + +/* No comment provided by engineer. */ +"Created at" = "Létrehozva ekkor"; + +/* copied message info */ +"Created at: %@" = "Létrehozva ekkor: %@"; + +/* No comment provided by engineer. */ +"Created on %@" = "Létrehozva %@"; + +/* No comment provided by engineer. */ +"Creating link…" = "Hivatkozás létrehozása…"; + +/* No comment provided by engineer. */ +"creator" = "szerző"; + +/* No comment provided by engineer. */ +"Current Passcode" = "Jelenlegi jelkód"; + +/* No comment provided by engineer. */ +"Current passphrase…" = "Jelenlegi jelmondat…"; + +/* No comment provided by engineer. */ +"Currently maximum supported file size is %@." = "Jelenleg a maximális támogatott fájlméret %@."; + +/* dropdown time picker choice */ +"custom" = "egyedi"; + +/* No comment provided by engineer. */ +"Custom time" = "Személyreszabott idő"; + +/* No comment provided by engineer. */ +"Dark" = "Sötét"; + +/* No comment provided by engineer. */ +"Database downgrade" = "Visszatérés a korábbi adatbázis verzióra"; + +/* No comment provided by engineer. */ +"Database encrypted!" = "Adatbázis titkosítva!"; + +/* No comment provided by engineer. */ +"Database encryption passphrase will be updated and stored in the keychain.\n" = "Az adatbázis titkosítási jelmondata frissül és tárolódik a kulcstárolóban.\n"; + +/* No comment provided by engineer. */ +"Database encryption passphrase will be updated.\n" = "Adatbázis titkosítási jelmondat frissítve lesz.\n"; + +/* No comment provided by engineer. */ +"Database error" = "Adatbázis hiba"; + +/* No comment provided by engineer. */ +"Database ID" = "Adatbázis ID"; + +/* copied message info */ +"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."; + +/* 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, megváltoztatható."; + +/* No comment provided by engineer. */ +"Database is encrypted using a random passphrase. Please change it before exporting." = "Az adatbázis egy véletlenszerű jelmondattal van titkosítva. Exportálás előtti módosítás szükséges."; + +/* No comment provided by engineer. */ +"Database passphrase" = "Adatbázis jelmondat"; + +/* No comment provided by engineer. */ +"Database passphrase & export" = "Adatbázis jelmondat és exportálás"; + +/* No comment provided by engineer. */ +"Database passphrase is different from saved in the keychain." = "Az adatbázis jelmondata eltér a kulcstárlóban mentettől."; + +/* No comment provided by engineer. */ +"Database passphrase is required to open chat." = "Adatbázis jelmondat szükséges a csevegés megnyitásához."; + +/* No comment provided by engineer. */ +"Database upgrade" = "Adatbázis fejlesztése"; + +/* No comment provided by engineer. */ +"database version is newer than the app, but no down migration for: %@" = "az adatbázis verziója újabb, mint az alkalmazásé, de nincs visszafelé migráció: %@"; + +/* No comment provided by engineer. */ +"Database will be encrypted and the passphrase stored in the keychain.\n" = "Az adatbázis titkosítva lesz, a jelmondat pedig a kulcstárolóban lesz tárolva.\n"; + +/* No comment provided by engineer. */ +"Database will be encrypted.\n" = "Az adatbázis titkosításra kerül.\n"; + +/* No comment provided by engineer. */ +"Database will be migrated when the app restarts" = "Az adatbázis az alkalmazás újraindításakor migrálásra kerül"; + +/* time unit */ +"days" = "nap"; + +/* No comment provided by engineer. */ +"Decentralized" = "Decentralizált"; + +/* message decrypt error item */ +"Decryption error" = "Titkosítás visszafejtési hiba"; + +/* pref value */ +"default (%@)" = "alapértelmezett (%@)"; + +/* No comment provided by engineer. */ +"default (no)" = "alapértelmezett (nem)"; + +/* No comment provided by engineer. */ +"default (yes)" = "alapértelmezett (igen)"; + +/* chat item action */ +"Delete" = "Törlés"; + +/* No comment provided by engineer. */ +"Delete %lld messages?" = "Töröl %lld üzenetet?"; + +/* No comment provided by engineer. */ +"Delete address" = "Azonosító törlése"; + +/* No comment provided by engineer. */ +"Delete address?" = "Azonosító törlése?"; + +/* No comment provided by engineer. */ +"Delete after" = "Törlés miután"; + +/* No comment provided by engineer. */ +"Delete all files" = "Minden fájl törlése"; + +/* No comment provided by engineer. */ +"Delete and notify contact" = "Törlés és ismerős értesítése"; + +/* No comment provided by engineer. */ +"Delete archive" = "Archívum törlése"; + +/* No comment provided by engineer. */ +"Delete chat archive?" = "Csevegési archívum törlése?"; + +/* No comment provided by engineer. */ +"Delete chat profile" = "Csevegési profil törlése"; + +/* No comment provided by engineer. */ +"Delete chat profile?" = "Csevegési profil törlése?"; + +/* No comment provided by engineer. */ +"Delete connection" = "Kapcsolat törlése"; + +/* No comment provided by engineer. */ +"Delete contact" = "Ismerős törlése"; + +/* No comment provided by engineer. */ +"Delete Contact" = "Ismerős törlése"; + +/* No comment provided by engineer. */ +"Delete contact?\nThis cannot be undone!" = "Ismerős törlése?\nEzt nem vonható vissza!"; + +/* No comment provided by engineer. */ +"Delete database" = "Adatbázis törlése"; + +/* server test step */ +"Delete file" = "Fájl törlése"; + +/* No comment provided by engineer. */ +"Delete files and media?" = "Fájlok és a médiatartalmak törlése?"; + +/* No comment provided by engineer. */ +"Delete files for all chat profiles" = "Fájlok törlése minden csevegési profilból"; + +/* chat feature */ +"Delete for everyone" = "Törlés mindenkinél"; + +/* No comment provided by engineer. */ +"Delete for me" = "Törlés nálam"; + +/* No comment provided by engineer. */ +"Delete group" = "Csoport törlése"; + +/* No comment provided by engineer. */ +"Delete group?" = "Csoport törlése?"; + +/* No comment provided by engineer. */ +"Delete invitation" = "Meghívó törlése"; + +/* No comment provided by engineer. */ +"Delete link" = "Hivatkozás törlése"; + +/* No comment provided by engineer. */ +"Delete link?" = "Hivatkozás törlése?"; + +/* No comment provided by engineer. */ +"Delete member message?" = "Csoporttag üzenet törlése?"; + +/* No comment provided by engineer. */ +"Delete message?" = "Üzenet törlése?"; + +/* No comment provided by engineer. */ +"Delete messages" = "Üzenetek törlése"; + +/* No comment provided by engineer. */ +"Delete messages after" = "Üzenetek törlése miután"; + +/* No comment provided by engineer. */ +"Delete old database" = "Régi adatbázis törlése"; + +/* No comment provided by engineer. */ +"Delete old database?" = "Régi adatbázis törlése?"; + +/* No comment provided by engineer. */ +"Delete pending connection" = "Függőben lévő kapcsolat törlése"; + +/* No comment provided by engineer. */ +"Delete pending connection?" = "Függő kapcsolatfelvételi kérések törlése?"; + +/* No comment provided by engineer. */ +"Delete profile" = "Profil törlése"; + +/* server test step */ +"Delete queue" = "Várólista törlése"; + +/* No comment provided by engineer. */ +"Delete user profile?" = "Felhasználói profil törlése?"; + +/* deleted chat item */ +"deleted" = "törölve"; + +/* No comment provided by engineer. */ +"Deleted at" = "Törölve ekkor"; + +/* copied message info */ +"Deleted at: %@" = "Törölve ekkor: %@"; + +/* rcv direct event chat item */ +"deleted contact" = "törölt ismerős"; + +/* rcv group event chat item */ +"deleted group" = "törölt csoport"; + +/* No comment provided by engineer. */ +"Delivery" = "Kézbesítés"; + +/* No comment provided by engineer. */ +"Delivery receipts are disabled!" = "Kézbesítési igazolások kikapcsolva!"; + +/* No comment provided by engineer. */ +"Delivery receipts!" = "Kézbesítési igazolások!"; + +/* No comment provided by engineer. */ +"Description" = "Leírás"; + +/* No comment provided by engineer. */ +"Desktop address" = "Számítógép azonosítója"; + +/* No comment provided by engineer. */ +"Desktop app version %@ is not compatible with this app." = "Az asztali kliens verziója %@ nem kompatibilis ezzel az alkalmazással."; + +/* No comment provided by engineer. */ +"Desktop devices" = "Számítógépek"; + +/* No comment provided by engineer. */ +"Develop" = "Fejlesztés"; + +/* No comment provided by engineer. */ +"Developer tools" = "Fejlesztői eszközök"; + +/* No comment provided by engineer. */ +"Device" = "Eszköz"; + +/* No comment provided by engineer. */ +"Device authentication is disabled. Turning off SimpleX Lock." = "Eszközhitelesítés kikapcsolva. SimpleX zárolás kikapcsolása."; + +/* No comment provided by engineer. */ +"Device authentication is not enabled. You can turn on SimpleX Lock via Settings, once you enable device authentication." = "Eszközhitelesítés nem engedélyezett.A SimpleX zárolás bekapcsolható a Beállításokon keresztül, miután az eszköz hitelesítés engedélyezésre került."; + +/* No comment provided by engineer. */ +"different migration in the app/database: %@ / %@" = "különböző migrációk 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ó."; + +/* connection level description */ +"direct" = "közvetlen"; + +/* chat feature */ +"Direct messages" = "Közvetlen üzenetek"; + +/* No comment provided by engineer. */ +"Direct messages between members are prohibited in this group." = "Ebben a csoportban tiltott a tagok közötti közvetlen üzenetek küldése."; + +/* No comment provided by engineer. */ +"Disable (keep overrides)" = "Letiltás (felülírások megtartásával)"; + +/* No comment provided by engineer. */ +"Disable for all" = "Letiltás mindenki számára"; + +/* authentication reason */ +"Disable SimpleX Lock" = "SimpleX zárolás kikapcsolása"; + +/* No comment provided by engineer. */ +"disabled" = "letiltva"; + +/* No comment provided by engineer. */ +"Disappearing message" = "Eltűnő üzenet"; + +/* chat feature */ +"Disappearing messages" = "Eltűnő üzenetek"; + +/* No comment provided by engineer. */ +"Disappearing messages are prohibited in this chat." = "Az eltűnő üzenetek le vannak tiltva ebben a csevegésben."; + +/* No comment provided by engineer. */ +"Disappearing messages are prohibited in this group." = "Az eltűnő üzenetek küldése le van tiltva ebben a csoportban."; + +/* No comment provided by engineer. */ +"Disappears at" = "Eltűnik ekkor"; + +/* copied message info */ +"Disappears at: %@" = "Eltűnik ekkor: %@"; + +/* server test step */ +"Disconnect" = "Kapcsolat bontása"; + +/* No comment provided by engineer. */ +"Disconnect desktop?" = "Számítógép leválasztása?"; + +/* No comment provided by engineer. */ +"Discover and join groups" = "Helyi csoportok felfedezése és csatlakozás"; + +/* No comment provided by engineer. */ +"Discover via local network" = "Felfedezés helyi hálózaton keresztül"; + +/* No comment provided by engineer. */ +"Do it later" = "Későbbre halaszt"; + +/* No comment provided by engineer. */ +"Do not send history to new members." = "Ne küldjön előzményeket új tagok részére."; + +/* No comment provided by engineer. */ +"Do NOT use SimpleX for emergency calls." = "NE használja a SimpleX-et segélyhívásokhoz."; + +/* No comment provided by engineer. */ +"Don't create address" = "Ne hozzon létre azonosítót"; + +/* No comment provided by engineer. */ +"Don't enable" = "Ne engedélyezze"; + +/* No comment provided by engineer. */ +"Don't show again" = "Ne mutasd újra"; + +/* No comment provided by engineer. */ +"Downgrade and open chat" = "Visszatérés a korábbi verzióra és a csevegés megnyitása"; + +/* server test step */ +"Download file" = "Fájl letöltése"; + +/* No comment provided by engineer. */ +"Duplicate display name!" = "Duplikált megjelenítési név!"; + +/* integrity error chat item */ +"duplicate message" = "duplikálódott üzenet"; + +/* No comment provided by engineer. */ +"Duration" = "Időtartam"; + +/* No comment provided by engineer. */ +"e2e encrypted" = "e2e titkosított"; + +/* chat item action */ +"Edit" = "Szerkesztés"; + +/* No comment provided by engineer. */ +"Edit group profile" = "A csoport profiljának szerkesztése"; + +/* No comment provided by engineer. */ +"Enable" = "Engedélyezés"; + +/* No comment provided by engineer. */ +"Enable (keep overrides)" = "Engedélyezés (felülírások megtartásával)"; + +/* No comment provided by engineer. */ +"Enable automatic message deletion?" = "Automatikus üzenet törlés engedélyezése?"; + +/* No comment provided by engineer. */ +"Enable camera access" = "Kamera hozzáférés engedélyezése"; + +/* No comment provided by engineer. */ +"Enable for all" = "Engedélyezés mindenki részére"; + +/* No comment provided by engineer. */ +"Enable instant notifications?" = "Azonnali értesítések engedélyezése?"; + +/* No comment provided by engineer. */ +"Enable lock" = "Zárolás engedélyezése"; + +/* No comment provided by engineer. */ +"Enable notifications" = "Értesítések engedélyezése"; + +/* No comment provided by engineer. */ +"Enable periodic notifications?" = "Időszakos értesítések engedélyezése?"; + +/* No comment provided by engineer. */ +"Enable self-destruct" = "Önmegsemmisítés engedélyezése"; + +/* set passcode view */ +"Enable self-destruct passcode" = "Önmegsemmisítő jelkód engedélyezése"; + +/* authentication reason */ +"Enable SimpleX Lock" = "SimpleX zárolás engedélyezése"; + +/* No comment provided by engineer. */ +"Enable TCP keep-alive" = "TCP életben tartásának engedélyezése"; + +/* enabled status */ +"enabled" = "engedélyezve"; + +/* enabled status */ +"enabled for contact" = "engedélyezve ismerős részére"; + +/* enabled status */ +"enabled for you" = "engedélyezve az ön számára"; + +/* No comment provided by engineer. */ +"Encrypt" = "Titkosít"; + +/* No comment provided by engineer. */ +"Encrypt database?" = "Adatbázis titkosítása?"; + +/* No comment provided by engineer. */ +"Encrypt local files" = "Helyi fájlok titkosítása"; + +/* No comment provided by engineer. */ +"Encrypt stored files & media" = "Tárolt fájlok és médiatartalmak titkosítása"; + +/* No comment provided by engineer. */ +"Encrypted database" = "Titkosított adatbázis"; + +/* notification */ +"Encrypted message or another event" = "Titkosított üzenet vagy más esemény"; + +/* notification */ +"Encrypted message: app is stopped" = "Titkosított üzenet: az alkalmazás leállt"; + +/* notification */ +"Encrypted message: database error" = "Titkosított üzenet: adatbázis hiba"; + +/* notification */ +"Encrypted message: database migration error" = "Titkosított üzenet: adatbázis-migrációs hiba"; + +/* notification */ +"Encrypted message: keychain error" = "Titkosított üzenet: kulcstároló hiba"; + +/* notification */ +"Encrypted message: no passphrase" = "Titkosított üzenet: nincs jelmondat"; + +/* notification */ +"Encrypted message: unexpected error" = "Titkosított üzenet: váratlan hiba"; + +/* chat item text */ +"encryption agreed" = "titkosítás egyeztetve"; + +/* chat item text */ +"encryption agreed for %@" = "titkosítás elfogadva %@ számára"; + +/* chat item text */ +"encryption ok" = "titkosítás rendben"; + +/* chat item text */ +"encryption ok for %@" = "titkosítás rendben vele: %@"; + +/* chat item text */ +"encryption re-negotiation allowed" = "titkosítás újraegyeztetés engedélyezve"; + +/* chat item text */ +"encryption re-negotiation allowed for %@" = "titkosítás újraegyeztetés engedélyezve vele: %@"; + +/* message decrypt error item */ +"Encryption re-negotiation error" = "Titkosítás újraegyeztetési hiba"; + +/* No comment provided by engineer. */ +"Encryption re-negotiation failed." = "Titkosítás újraegyeztetése sikertelen."; + +/* chat item text */ +"encryption re-negotiation required" = "titkosítás újraegyeztetés szükséges"; + +/* chat item text */ +"encryption re-negotiation required for %@" = "titkosítás újraegyeztetés szükséges %@ számára"; + +/* No comment provided by engineer. */ +"ended" = "befejeződött"; + +/* call status */ +"ended call %@" = "%@ hívása befejeződött"; + +/* No comment provided by engineer. */ +"Enter correct passphrase." = "Helyes jelmondat bevitele."; + +/* No comment provided by engineer. */ +"Enter group name…" = "Csoportnév megadása…"; + +/* No comment provided by engineer. */ +"Enter Passcode" = "Jelkód megadása"; + +/* No comment provided by engineer. */ +"Enter passphrase…" = "Jelmondat megadása…"; + +/* No comment provided by engineer. */ +"Enter password above to show!" = "Jelszó megadása a megjelenítéshez!"; + +/* No comment provided by engineer. */ +"Enter server manually" = "Kiszolgáló megadása kézzel"; + +/* No comment provided by engineer. */ +"Enter this device name…" = "Eszköznév megadása…"; + +/* placeholder */ +"Enter welcome message…" = "Üdvözlő üzenetet megadása…"; + +/* placeholder */ +"Enter welcome message… (optional)" = "Üdvözlő üzenetet megadása… (opcionális)"; + +/* No comment provided by engineer. */ +"Enter your name…" = "Adja meg nevét…"; + +/* No comment provided by engineer. */ +"error" = "hiba"; + +/* No comment provided by engineer. */ +"Error" = "Hiba"; + +/* No comment provided by engineer. */ +"Error aborting address change" = "Hiba az azonosító megváltoztatásának megszakításakor"; + +/* No comment provided by engineer. */ +"Error accepting contact request" = "Hiba történt a kapcsolatfelvételi kérelem elfogadásakor"; + +/* No comment provided by engineer. */ +"Error accessing database file" = "Hiba az adatbázisfájl elérésekor"; + +/* No comment provided by engineer. */ +"Error adding member(s)" = "Hiba a tag(-ok) hozzáadásakor"; + +/* No comment provided by engineer. */ +"Error changing address" = "Hiba az azonosító megváltoztatásakor"; + +/* No comment provided by engineer. */ +"Error changing role" = "Hiba a szerepkör megváltoztatásakor"; + +/* No comment provided by engineer. */ +"Error changing setting" = "Hiba a beállítás megváltoztatásakor"; + +/* No comment provided by engineer. */ +"Error creating address" = "Hiba az azonosító létrehozásakor"; + +/* No comment provided by engineer. */ +"Error creating group" = "Hiba a csoport létrehozásakor"; + +/* No comment provided by engineer. */ +"Error creating group link" = "Hiba a csoport hivatkozásának létrehozásakor"; + +/* No comment provided by engineer. */ +"Error creating member contact" = "Hiba az ismerőssel történő kapcsolat létrehozásában"; + +/* No comment provided by engineer. */ +"Error creating message" = "Hiba az üzenet létrehozásakor"; + +/* No comment provided by engineer. */ +"Error creating profile!" = "Hiba a profil létrehozásakor!"; + +/* No comment provided by engineer. */ +"Error decrypting file" = "Hiba a fájl visszafejtésekor"; + +/* No comment provided by engineer. */ +"Error deleting chat database" = "Hiba a csevegési adatbázis törlésekor"; + +/* No comment provided by engineer. */ +"Error deleting chat!" = "Hiba a csevegés törlésekor!"; + +/* No comment provided by engineer. */ +"Error deleting connection" = "Hiba a kapcsolat törlésekor"; + +/* No comment provided by engineer. */ +"Error deleting contact" = "Hiba az ismerős törlésekor"; + +/* No comment provided by engineer. */ +"Error deleting database" = "Hiba az adatbázis törlésekor"; + +/* No comment provided by engineer. */ +"Error deleting old database" = "Hiba a régi adatbázis törlésekor"; + +/* No comment provided by engineer. */ +"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"; + +/* No comment provided by engineer. */ +"Error enabling delivery receipts!" = "Hiba a kézbesítési jelentések engedélyezésekor!"; + +/* No comment provided by engineer. */ +"Error enabling notifications" = "Hiba az értesítések engedélyezésekor"; + +/* No comment provided by engineer. */ +"Error encrypting database" = "Hiba az adatbázis titkosításakor"; + +/* No comment provided by engineer. */ +"Error exporting chat database" = "Hiba a csevegési adatbázis exportálásakor"; + +/* No comment provided by engineer. */ +"Error importing chat database" = "Hiba a csevegési adatbázis importálásakor"; + +/* No comment provided by engineer. */ +"Error joining group" = "Hiba a csoporthoz való csatlakozáskor"; + +/* No comment provided by engineer. */ +"Error loading %@ servers" = "Hiba a %@ kiszolgálók betöltésekor"; + +/* No comment provided by engineer. */ +"Error opening chat" = "Hiba a csevegés megnyitásakor"; + +/* No comment provided by engineer. */ +"Error receiving file" = "Hiba a fájl fogadásakor"; + +/* No comment provided by engineer. */ +"Error removing member" = "Hiba a tag eltávolításakor"; + +/* No comment provided by engineer. */ +"Error saving %@ servers" = "Hiba történt a %@ kiszolgálók mentése közben"; + +/* No comment provided by engineer. */ +"Error saving group profile" = "Hiba a csoport profil mentésekor"; + +/* No comment provided by engineer. */ +"Error saving ICE servers" = "Hiba az ICE kiszolgálók mentésekor"; + +/* No comment provided by engineer. */ +"Error saving passcode" = "Hiba a jelkód mentése közben"; + +/* No comment provided by engineer. */ +"Error saving passphrase to keychain" = "Hiba a jelmondat kulcstárolóba történő mentésekor"; + +/* No comment provided by engineer. */ +"Error saving user password" = "Hiba a felhasználó jelszavának mentésekor"; + +/* No comment provided by engineer. */ +"Error scanning code: %@" = "Hiba a kód beolvasása közben: %@"; + +/* No comment provided by engineer. */ +"Error sending email" = "Hiba az e-mail küldésekor"; + +/* No comment provided by engineer. */ +"Error sending member contact invitation" = "Hiba történt a tag kapcsolatfelvételi meghívójának elküldésekor"; + +/* No comment provided by engineer. */ +"Error sending message" = "Hiba az üzenet küldésekor"; + +/* No comment provided by engineer. */ +"Error setting delivery receipts!" = "Hiba történt a kézbesítési igazolások beállításakor!"; + +/* No comment provided by engineer. */ +"Error starting chat" = "Hiba a csevegés elindításakor"; + +/* No comment provided by engineer. */ +"Error stopping chat" = "Hiba a csevegés megállításakor"; + +/* No comment provided by engineer. */ +"Error switching profile!" = "Hiba a profil váltásakor!"; + +/* No comment provided by engineer. */ +"Error synchronizing connection" = "Hiba a kapcsolat szinkronizálása során"; + +/* No comment provided by engineer. */ +"Error updating group link" = "Hiba a csoport hivatkozás frissítésekor"; + +/* No comment provided by engineer. */ +"Error updating message" = "Hiba az üzenet frissítésekor"; + +/* No comment provided by engineer. */ +"Error updating settings" = "Hiba történt a beállítások frissítésekor"; + +/* No comment provided by engineer. */ +"Error updating user privacy" = "Hiba a felhasználói beállítások frissítésekor"; + +/* No comment provided by engineer. */ +"Error: " = "Hiba: "; + +/* No comment provided by engineer. */ +"Error: %@" = "Hiba: %@"; + +/* No comment provided by engineer. */ +"Error: no database file" = "Hiba: nincs adatbázis fájl"; + +/* No comment provided by engineer. */ +"Error: URL is invalid" = "Hiba: az URL érvénytelen"; + +/* No comment provided by engineer. */ +"Even when disabled in the conversation." = "Akkor is, ha le van tiltva a beszélgetésben."; + +/* No comment provided by engineer. */ +"event happened" = "esemény történt"; + +/* No comment provided by engineer. */ +"Exit without saving" = "Kilépés mentés nélkül"; + +/* chat item action */ +"Expand" = "Kibontás"; + +/* No comment provided by engineer. */ +"Export database" = "Adatbázis exportálása"; + +/* No comment provided by engineer. */ +"Export error:" = "Exportálási hiba:"; + +/* No comment provided by engineer. */ +"Exported database archive." = "Exportált adatbázis-archívum."; + +/* No comment provided by engineer. */ +"Exporting database archive…" = "Adatbázis archívum exportálása…"; + +/* No comment provided by engineer. */ +"Failed to remove passphrase" = "Nem sikerült eltávolítani a jelmondatot"; + +/* No comment provided by engineer. */ +"Fast and no wait until the sender is online!" = "Gyors és nem kell várni, amíg a feladó online lesz!"; + +/* No comment provided by engineer. */ +"Faster joining and more reliable messages." = "Gyorsabb csatlakozás és megbízhatóbb üzenet kézbesítés."; + +/* No comment provided by engineer. */ +"Favorite" = "Kedvenc"; + +/* No comment provided by engineer. */ +"File will be deleted from servers." = "A fájl törölve lesz a kiszolgálóról."; + +/* No comment provided by engineer. */ +"File will be received when your contact completes uploading it." = "A fájl akkor érkezik meg, amikor ismerőse befejezte annak feltöltést."; + +/* No comment provided by engineer. */ +"File will be received when your contact is online, please wait or check later!" = "A fájl akkor érkezik meg, amint ismerőse online lesz, várjon, vagy ellenőrizze később!"; + +/* No comment provided by engineer. */ +"File: %@" = "Fájl: %@"; + +/* No comment provided by engineer. */ +"Files & media" = "Fájlok és média"; + +/* chat feature */ +"Files and media" = "Fájlok és médiatartalom"; + +/* No comment provided by engineer. */ +"Files and media are prohibited in this group." = "A fájlok- és a médiatartalom küldése le van tiltva ebben a csoportban."; + +/* No comment provided by engineer. */ +"Files and media prohibited!" = "A fájlok- és a médiatartalom küldése le van tiltva!"; + +/* No comment provided by engineer. */ +"Filter unread and favorite chats." = "Olvasatlan és kedvenc csevegésekre való szűrés."; + +/* No comment provided by engineer. */ +"Finally, we have them! 🚀" = "Végre, megvannak! 🚀"; + +/* No comment provided by engineer. */ +"Find chats faster" = "Csevegési üzenetek gyorsabb megtalálása"; + +/* No comment provided by engineer. */ +"Fix" = "Javítás"; + +/* No comment provided by engineer. */ +"Fix connection" = "Kapcsolat javítása"; + +/* No comment provided by engineer. */ +"Fix connection?" = "Kapcsolat javítása?"; + +/* No comment provided by engineer. */ +"Fix encryption after restoring backups." = "Titkosítás javítása az adatmentések helyreállítása után."; + +/* No comment provided by engineer. */ +"Fix not supported by contact" = "Ismerős általi javítás nem támogatott"; + +/* No comment provided by engineer. */ +"Fix not supported by group member" = "Csoporttag általi javítás nem támogatott"; + +/* No comment provided by engineer. */ +"For console" = "Konzolhoz"; + +/* No comment provided by engineer. */ +"Found desktop" = "Megtalált számítógép"; + +/* No comment provided by engineer. */ +"French interface" = "Francia kezelőfelület"; + +/* No comment provided by engineer. */ +"Full link" = "Teljes hivatkozás"; + +/* No comment provided by engineer. */ +"Full name (optional)" = "Teljes név (opcionális)"; + +/* No comment provided by engineer. */ +"Full name:" = "Teljes név:"; + +/* No comment provided by engineer. */ +"Fully decentralized – visible only to members." = "Teljesen decentralizált - kizárólag tagok számára látható."; + +/* No comment provided by engineer. */ +"Fully re-implemented - work in background!" = "Teljesen újra implementálva - háttérben történő működés!"; + +/* No comment provided by engineer. */ +"Further reduced battery usage" = "Tovább csökkentett akkumulátor használat"; + +/* No comment provided by engineer. */ +"GIFs and stickers" = "GIF-ek és matricák"; + +/* No comment provided by engineer. */ +"Group" = "Csoport"; + +/* No comment provided by engineer. */ +"Group already exists" = "A csoport már létezik"; + +/* No comment provided by engineer. */ +"Group already exists!" = "A csoport már létezik!"; + +/* No comment provided by engineer. */ +"group deleted" = "a csoport törölve"; + +/* No comment provided by engineer. */ +"Group display name" = "A csoport megjelenített neve"; + +/* No comment provided by engineer. */ +"Group full name (optional)" = "Csoport teljes neve (opcionális)"; + +/* No comment provided by engineer. */ +"Group image" = "Csoportkép"; + +/* No comment provided by engineer. */ +"Group invitation" = "Csoportos meghívó"; + +/* No comment provided by engineer. */ +"Group invitation expired" = "A csoport meghívó lejárt"; + +/* No comment provided by engineer. */ +"Group invitation is no longer valid, it was removed by sender." = "A csoport meghívó már nem érvényes, el lett távolítva a küldője által."; + +/* No comment provided by engineer. */ +"Group link" = "Csoport hivatkozás"; + +/* No comment provided by engineer. */ +"Group links" = "Csoport hivatkozások"; + +/* No comment provided by engineer. */ +"Group members can add message reactions." = "Csoporttagok üzenetreakciókat adhatnak hozzá."; + +/* No comment provided by engineer. */ +"Group members can irreversibly delete sent messages. (24 hours)" = "Csoporttagok visszafordíthatatlanul törölhetik az elküldött üzeneteket. (24 óra)"; + +/* No comment provided by engineer. */ +"Group members can send direct messages." = "Csoporttagok küldhetnek közvetlen üzeneteket."; + +/* No comment provided by engineer. */ +"Group members can send disappearing messages." = "Csoporttagok küldhetnek eltűnő üzeneteket."; + +/* No comment provided by engineer. */ +"Group members can send files and media." = "Csoporttagok küldhetnek fájlokat és médiatartalmakat."; + +/* No comment provided by engineer. */ +"Group members can send voice messages." = "Csoporttagok küldhetnek hangüzeneteket."; + +/* notification */ +"Group message:" = "Csoport üzenet:"; + +/* No comment provided by engineer. */ +"Group moderation" = "Csoport moderáció"; + +/* No comment provided by engineer. */ +"Group preferences" = "Csoport beállítások"; + +/* No comment provided by engineer. */ +"Group profile" = "Csoport profil"; + +/* No comment provided by engineer. */ +"Group profile is stored on members' devices, not on the servers." = "A csoport profilja a tagok eszközein tárolódik, nem a kiszolgálókon."; + +/* snd group event chat item */ +"group profile updated" = "csoport profil frissítve"; + +/* No comment provided by engineer. */ +"Group welcome message" = "Csoport üdvözlő üzenete"; + +/* No comment provided by engineer. */ +"Group will be deleted for all members - this cannot be undone!" = "Csoport törlésre kerül minden tag számára - ez nem vonható vissza!"; + +/* No comment provided by engineer. */ +"Group will be deleted for you - this cannot be undone!" = "A csoport törlésre kerül az ön részére - ez nem vonható vissza!"; + +/* No comment provided by engineer. */ +"Help" = "Segítség"; + +/* No comment provided by engineer. */ +"Hidden" = "Rejtett"; + +/* No comment provided by engineer. */ +"Hidden chat profiles" = "Rejtett csevegési profilok"; + +/* No comment provided by engineer. */ +"Hidden profile password" = "Rejtett profil jelszó"; + +/* chat item action */ +"Hide" = "Elrejt"; + +/* 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."; + +/* No comment provided by engineer. */ +"Hide profile" = "Profil elrejtése"; + +/* No comment provided by engineer. */ +"Hide:" = "Elrejt:"; + +/* No comment provided by engineer. */ +"History" = "Előzmények"; + +/* No comment provided by engineer. */ +"History is not sent to new members." = "Az előzmények nem kerülnek elküldésre új tagok részére."; + +/* time unit */ +"hours" = "óra"; + +/* No comment provided by engineer. */ +"How it works" = "Hogyan működik"; + +/* No comment provided by engineer. */ +"How SimpleX works" = "Hogyan működik a SimpleX"; + +/* No comment provided by engineer. */ +"How to" = "Hogyan"; + +/* No comment provided by engineer. */ +"How to use it" = "Hogyan használja"; + +/* No comment provided by engineer. */ +"How to use your servers" = "Kiszolgálók használata"; + +/* No comment provided by engineer. */ +"ICE servers (one per line)" = "ICE-kiszolgálók (soronként egy)"; + +/* No comment provided by engineer. */ +"If you can't meet in person, show QR code in a video call, or share the link." = "Ha nem tud személyesen találkozni, mutassa meg a QR-kódot egy videohívás során, vagy ossza meg a hivatkozást."; + +/* No comment provided by engineer. */ +"If you enter this passcode when opening the app, all app data will be irreversibly removed!" = "Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat visszafordíthatatlanul törlődik!"; + +/* No comment provided by engineer. */ +"If you enter your self-destruct passcode while opening the app:" = "Ha az alkalmazás megnyitásakor az önmegsemmisítő jelkódot megadásra kerül:"; + +/* 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 a ** Csináld később** elemre (az alkalmazás újraindításakor felajánlásra kerül az adatbázis áttelepítése)."; + +/* No comment provided by engineer. */ +"Ignore" = "Figyelmen kívül hagyás"; + +/* No comment provided by engineer. */ +"Image will be received when your contact completes uploading it." = "A kép akkor érkezik meg, amikor ismerőse befejezte annak feltöltését."; + +/* No comment provided by engineer. */ +"Image will be received when your contact is online, please wait or check later!" = "A kép akkor érkezik meg, amikor ismerős elérhető lesz, várjon vagy ellenőrizze később!"; + +/* No comment provided by engineer. */ +"Immediately" = "Azonnal"; + +/* No comment provided by engineer. */ +"Immune to spam and abuse" = "Spam és visszaélések elleni védelem"; + +/* No comment provided by engineer. */ +"Import" = "Importálás"; + +/* No comment provided by engineer. */ +"Import chat database?" = "Csevegési adatbázis importálása?"; + +/* No comment provided by engineer. */ +"Import database" = "Adatbázis importálása"; + +/* No comment provided by engineer. */ +"Improved message delivery" = "Továbbfejlesztett üzenetküldés"; + +/* No comment provided by engineer. */ +"Improved privacy and security" = "Fejlesztett adatvédelem és biztonság"; + +/* No comment provided by engineer. */ +"Improved server configuration" = "Javított kiszolgáló konfiguráció"; + +/* No comment provided by engineer. */ +"In reply to" = "Válasz neki"; + +/* No comment provided by engineer. */ +"Incognito" = "Inkognitó"; + +/* No comment provided by engineer. */ +"Incognito groups" = "Inkognitó csoportok"; + +/* No comment provided by engineer. */ +"Incognito mode" = "Inkognitó mód"; + +/* No comment provided by engineer. */ +"Incognito mode protects your privacy by using a new random profile for each contact." = "Az inkognitómód védi személyes adatait azáltal, hogy minden ismerőshöz új véletlenszerű profilt használ."; + +/* chat list item description */ +"incognito via contact address link" = "inkognitó a kapcsolattartási hivatkozáson keresztül"; + +/* chat list item description */ +"incognito via group link" = "inkognitó a csoportos hivatkozáson keresztül"; + +/* chat list item description */ +"incognito via one-time link" = "inkognitó egyszer használatos hivatkozáson keresztül"; + +/* notification */ +"Incoming audio call" = "Bejövő hanghívás"; + +/* notification */ +"Incoming call" = "Bejövő hívás"; + +/* notification */ +"Incoming video call" = "Bejövő videóhívás"; + +/* No comment provided by engineer. */ +"Incompatible database version" = "Nem kompatibilis adatbázis verzió"; + +/* No comment provided by engineer. */ +"Incompatible version" = "Nem kompatibilis verzió"; + +/* PIN entry */ +"Incorrect passcode" = "Téves jelkód"; + +/* No comment provided by engineer. */ +"Incorrect security code!" = "Helytelen biztonsági kód!"; + +/* connection level description */ +"indirect (%d)" = "közvetett (%d)"; + +/* chat item action */ +"Info" = "Információ"; + +/* No comment provided by engineer. */ +"Initial role" = "Kezdeti szerepkör"; + +/* No comment provided by engineer. */ +"Install [SimpleX Chat for terminal](https://github.com/simplex-chat/simplex-chat)" = "A [SimpleX Chat terminálhoz] telepítése (https://github.com/simplex-chat/simplex-chat)"; + +/* No comment provided by engineer. */ +"Instant push notifications will be hidden!\n" = "Az azonnali push értesítések elrejtésre kerülnek!\n"; + +/* No comment provided by engineer. */ +"Instantly" = "Azonnal"; + +/* No comment provided by engineer. */ +"Interface" = "Felület"; + +/* invalid chat data */ +"invalid chat" = "érvénytelen csevegés"; + +/* No comment provided by engineer. */ +"invalid chat data" = "érvénytelen csevegés adat"; + +/* No comment provided by engineer. */ +"Invalid connection link" = "Érvénytelen kapcsolati hivatkozás"; + +/* invalid chat item */ +"invalid data" = "érvénytelen adat"; + +/* No comment provided by engineer. */ +"Invalid display name!" = "Érvénytelen megjelenítendő felhaszálónév!"; + +/* No comment provided by engineer. */ +"Invalid link" = "Érvénytelen hivatkozás"; + +/* No comment provided by engineer. */ +"Invalid name!" = "Érvénytelen név!"; + +/* No comment provided by engineer. */ +"Invalid QR code" = "Érvénytelen QR-kód"; + +/* No comment provided by engineer. */ +"Invalid response" = "Érvénytelen válasz"; + +/* No comment provided by engineer. */ +"Invalid server address!" = "Érvénytelen kiszolgálócím!"; + +/* item status text */ +"Invalid status" = "Érvénytelen állapot"; + +/* No comment provided by engineer. */ +"Invitation expired!" = "A meghívó lejárt!"; + +/* group name */ +"invitation to group %@" = "meghívás a(z) %@ csoportba"; + +/* No comment provided by engineer. */ +"Invite friends" = "Barátok meghívása"; + +/* No comment provided by engineer. */ +"Invite members" = "Tagok meghívása"; + +/* No comment provided by engineer. */ +"Invite to group" = "Meghívás a csoportba"; + +/* No comment provided by engineer. */ +"invited" = "meghívott"; + +/* rcv group event chat item */ +"invited %@" = "%@ meghívott"; + +/* chat list item title */ +"invited to connect" = "meghívott, hogy csatlakozzon"; + +/* rcv group event chat item */ +"invited via your group link" = "meghívott a csoport hivatkozásán keresztül"; + +/* No comment provided by engineer. */ +"iOS Keychain is used to securely store passphrase - it allows receiving push notifications." = "Az iOS kulcstár a jelmondat biztonságos tárolására szolgál - lehetővé teszi a push-értesítések fogadását."; + +/* No comment provided by engineer. */ +"iOS Keychain will be used to securely store passphrase after you restart the app or change passphrase - it will allow receiving push notifications." = "Az iOS kulcstár az alkalmazás újraindítása, vagy a jelmondat módosítása után a jelmondat biztonságos tárolására szolgál - lehetővé teszi a push-értesítések fogadását."; + +/* No comment provided by engineer. */ +"Irreversible message deletion" = "Visszafordíthatatlan üzenettörlés"; + +/* No comment provided by engineer. */ +"Irreversible message deletion is prohibited in this chat." = "Ebben a csevegésben az üzenetek visszafordíthatatlan törlése le van tiltva."; + +/* No comment provided by engineer. */ +"Irreversible message deletion is prohibited in this group." = "Ebben a csoportban az üzenetek visszafordíthatatlan törlése le van tiltva."; + +/* No comment provided by engineer. */ +"It allows having many anonymous connections without any shared data between them in a single chat profile." = "Lehetővé teszi, hogy egyetlen csevegőprofilon belül több anonim kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük."; + +/* No comment provided by engineer. */ +"It can happen when you or your connection used the old database backup." = "Ez akkor fordulhat elő, ha ön vagy a kapcsolata régi adatbázis biztonsági mentést használt."; + +/* No comment provided by engineer. */ +"It can happen when:\n1. The messages expired in the sending client after 2 days or on the server after 30 days.\n2. Message decryption failed, because you or your contact used old database backup.\n3. The connection was compromised." = "Ez akkor fordulhat elő, ha:\n1. Az üzenetek 2 nap után, vagy a kiszolgálón 30 nap után lejártak.\n2. Az üzenet visszafejtése sikertelen volt, mert vagy az ismerőse régebbi adatbázis biztonsági mentést használt.\n3. A kapcsolat sérült."; + +/* No comment provided by engineer. */ +"It seems like you are already connected via this link. If it is not the case, there was an error (%@)." = "Úgy tűnik, már csatlakozott ezen a hivatkozáson keresztül. Ha ez nem így van, akkor hiba történt (%@)."; + +/* No comment provided by engineer. */ +"Italian interface" = "Olasz kezelőfelület"; + +/* No comment provided by engineer. */ +"italic" = "dőlt"; + +/* No comment provided by engineer. */ +"Japanese interface" = "Japán kezelőfelület"; + +/* No comment provided by engineer. */ +"Join" = "Csatlakozás"; + +/* No comment provided by engineer. */ +"join as %@" = "csatlakozás mint %@"; + +/* No comment provided by engineer. */ +"Join group" = "Csatlakozás csoporthoz"; + +/* No comment provided by engineer. */ +"Join group conversations" = "Csatlakozás csoportos beszélgetésekhez"; + +/* No comment provided by engineer. */ +"Join group?" = "Csatlakozik a csoporthoz?"; + +/* No comment provided by engineer. */ +"Join incognito" = "Csatlakozás inkognitóban"; + +/* No comment provided by engineer. */ +"Join with current profile" = "Csatlakozás a jelenlegi profillal"; + +/* No comment provided by engineer. */ +"Join your group?\nThis is your link for group %@!" = "Csatlakozik a csoportjához?\nEz az ön hivatkozása a(z) %@ csoporthoz!"; + +/* No comment provided by engineer. */ +"Joining group" = "Csatlakozás a csoporthoz"; + +/* No comment provided by engineer. */ +"Keep" = "Megtart"; + +/* No comment provided by engineer. */ +"Keep the app open to use it from desktop" = "A számítógépről való használathoz tartsd nyitva az alkalmazást"; + +/* No comment provided by engineer. */ +"Keep unused invitation?" = "Fel nem használt meghívó megtartása?"; + +/* No comment provided by engineer. */ +"Keep your connections" = "Kapcsolatok megtartása"; + +/* No comment provided by engineer. */ +"Keychain error" = "Kulcstároló hiba"; + +/* No comment provided by engineer. */ +"KeyChain error" = "Kulcstároló hiba"; + +/* No comment provided by engineer. */ +"Large file!" = "Nagy fájl!"; + +/* No comment provided by engineer. */ +"Learn more" = "Tudjon meg többet"; + +/* No comment provided by engineer. */ +"Leave" = "Elhagy"; + +/* No comment provided by engineer. */ +"Leave group" = "Csoport elhagyása"; + +/* No comment provided by engineer. */ +"Leave group?" = "Csoport elhagyása?"; + +/* rcv group event chat item */ +"left" = "elhagyta"; + +/* email subject */ +"Let's talk in SimpleX Chat" = "Beszélgessünk a SimpleX Chat-ben"; + +/* No comment provided by engineer. */ +"Light" = "Világos"; + +/* No comment provided by engineer. */ +"Limitations" = "Korlátozások"; + +/* No comment provided by engineer. */ +"Link mobile and desktop apps! 🔗" = "Társítsa össze a mobil és az asztali alkalmazásokat! 🔗"; + +/* No comment provided by engineer. */ +"Linked desktop options" = "Összekapcsolt számítógép beállítások"; + +/* No comment provided by engineer. */ +"Linked desktops" = "Összekapcsolt számítógépek"; + +/* No comment provided by engineer. */ +"LIVE" = "ÉLŐ"; + +/* No comment provided by engineer. */ +"Live message!" = "Élő üzenet!"; + +/* No comment provided by engineer. */ +"Live messages" = "Élő üzenetek"; + +/* No comment provided by engineer. */ +"Local" = "Helyi"; + +/* No comment provided by engineer. */ +"Local name" = "Helyi név"; + +/* No comment provided by engineer. */ +"Local profile data only" = "Csak helyi profiladatok"; + +/* No comment provided by engineer. */ +"Lock after" = "Zárolás miután"; + +/* No comment provided by engineer. */ +"Lock mode" = "Zárolási mód"; + +/* No comment provided by engineer. */ +"Make a private connection" = "Privát kapcsolat létrehozása"; + +/* No comment provided by engineer. */ +"Make one message disappear" = "Egy üzenet eltüntetése"; + +/* No comment provided by engineer. */ +"Make profile private!" = "Tegye priváttá profilját!"; + +/* No comment provided by engineer. */ +"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Győződjön meg arról, hogy a %@ szervercímek megfelelő formátumúak, sorszeparáltak és nem duplikáltak (%@)."; + +/* No comment provided by engineer. */ +"Make sure WebRTC ICE server addresses are in correct format, line separated and are not duplicated." = "Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nem duplikáltak."; + +/* 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-nek nincsenek felhasználói 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"; + +/* No comment provided by engineer. */ +"Mark read" = "Megjelölés olvasottként"; + +/* No comment provided by engineer. */ +"Mark verified" = "Ellenőrzöttként jelölve"; + +/* No comment provided by engineer. */ +"Markdown in messages" = "Markdown az üzenetekben"; + +/* marked deleted chat item preview text */ +"marked deleted" = "töröltnek jelölve"; + +/* No comment provided by engineer. */ +"Max 30 seconds, received instantly." = "Max. 30 másodperc, azonnal érkezett."; + +/* member role */ +"member" = "tag"; + +/* No comment provided by engineer. */ +"Member" = "Tag"; + +/* profile update event chat item */ +"member %@ changed to %@" = "%1$@ tag megváltoztatta a nevét erre: %2$@"; + +/* rcv group event chat item */ +"member connected" = "kapcsolódva"; + +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". All group members will be notified." = "A tag szerepköre meg fog változni erre: \"%@\". A csoport minden tagja értesítést kap róla."; + +/* No comment provided by engineer. */ +"Member role will be changed to \"%@\". The member will receive a new invitation." = "A tag szerepköre meg fog változni erre: \"%@\". A tag új meghívást fog kapni."; + +/* No comment provided by engineer. */ +"Member will be removed from group - this cannot be undone!" = "A tag eltávolítása a csoportból - ez nem vonható vissza!"; + +/* item status text */ +"Message delivery error" = "Üzenetkézbesítési hiba"; + +/* No comment provided by engineer. */ +"Message delivery receipts!" = "Üzenetkézbesítési bizonylatok!"; + +/* No comment provided by engineer. */ +"Message draft" = "Üzenetvázlat"; + +/* chat feature */ +"Message reactions" = "Üzenetreakciók"; + +/* No comment provided by engineer. */ +"Message reactions are prohibited in this chat." = "Az üzenetreakciók ebben a csevegésben le vannak tiltva."; + +/* No comment provided by engineer. */ +"Message reactions are prohibited in this group." = "Ebben a csoportban az üzenetreakciók le vannak tiltva."; + +/* notification */ +"message received" = "üzenet érkezett"; + +/* No comment provided by engineer. */ +"Message text" = "Üzenet szövege"; + +/* No comment provided by engineer. */ +"Messages" = "Üzenetek"; + +/* No comment provided by engineer. */ +"Messages & files" = "Üzenetek és fájlok"; + +/* No comment provided by engineer. */ +"Messages from %@ will be shown!" = "A(z) %@ által írt üzenetek megjelennek!"; + +/* No comment provided by engineer. */ +"Migrating database archive…" = "Adatbázis archívum migrálása…"; + +/* No comment provided by engineer. */ +"Migration error:" = "Migrációs hiba:"; + +/* No comment provided by engineer. */ +"Migration failed. Tap **Skip** below to continue using the current database. Please report the issue to the app developers via chat or email [chat@simplex.chat](mailto:chat@simplex.chat)." = "Sikertelen migráció. Koppintson a **Kihagyás** lehetőségre az aktuális adatbázis használatának folytatásához. Kérjük, jelentse a problémát az alkalmazás fejlesztőinek csevegésben vagy e-mailben [chat@simplex.chat](mailto:chat@simplex.chat)."; + +/* No comment provided by engineer. */ +"Migration is completed" = "A migráció befejeződött"; + +/* No comment provided by engineer. */ +"Migrations: %@" = "Migrációk: %@"; + +/* time unit */ +"minutes" = "perc"; + +/* call status */ +"missed call" = "nem fogadott hívás"; + +/* chat item action */ +"Moderate" = "Moderálás"; + +/* moderated chat item */ +"moderated" = "moderált"; + +/* No comment provided by engineer. */ +"Moderated at" = "Moderálva ekkor"; + +/* copied message info */ +"Moderated at: %@" = "Moderálva ekkor: %@"; + +/* marked deleted chat item preview text */ +"moderated by %@" = "%@ által moderálva"; + +/* time unit */ +"months" = "hónap"; + +/* No comment provided by engineer. */ +"More improvements are coming soon!" = "Hamarosan további fejlesztések érkeznek!"; + +/* item status description */ +"Most likely this connection is deleted." = "Valószínűleg ez a kapcsolat törlésre került."; + +/* No comment provided by engineer. */ +"Most likely this contact has deleted the connection with you." = "Valószínűleg ez az ismerős törölte önnel a kapcsolatot."; + +/* No comment provided by engineer. */ +"Multiple chat profiles" = "Több csevegőprofil"; + +/* No comment provided by engineer. */ +"Mute" = "Elnémítás"; + +/* No comment provided by engineer. */ +"Muted when inactive!" = "Némítás, ha inaktív!"; + +/* No comment provided by engineer. */ +"Name" = "Név"; + +/* No comment provided by engineer. */ +"Network & servers" = "Hálózat és kiszolgálók"; + +/* No comment provided by engineer. */ +"Network settings" = "Hálózati beállítások"; + +/* No comment provided by engineer. */ +"Network status" = "Hálózat állapota"; + +/* No comment provided by engineer. */ +"never" = "soha"; + +/* No comment provided by engineer. */ +"New chat" = "Új beszélgetés"; + +/* notification */ +"New contact request" = "Új kapcsolattartási kérelem"; + +/* notification */ +"New contact:" = "Új kapcsolat:"; + +/* No comment provided by engineer. */ +"New database archive" = "Új adatbázis-archívum"; + +/* No comment provided by engineer. */ +"New desktop app!" = "Új asztali alkalmazás!"; + +/* No comment provided by engineer. */ +"New display name" = "Új megjelenítési név"; + +/* No comment provided by engineer. */ +"New in %@" = "Újdonságok a(z) %@ verzióban"; + +/* No comment provided by engineer. */ +"New member role" = "Új tag szerepköre"; + +/* notification */ +"new message" = "új üzenet"; + +/* notification */ +"New message" = "Új üzenet"; + +/* No comment provided by engineer. */ +"New Passcode" = "Új jelkód"; + +/* No comment provided by engineer. */ +"New passphrase…" = "Új jelmondat…"; + +/* pref value */ +"no" = "nem"; + +/* No comment provided by engineer. */ +"No" = "Nem"; + +/* Authentication unavailable */ +"No app password" = "Nincs alkalmazás jelszó"; + +/* No comment provided by engineer. */ +"No contacts selected" = "Nem kerültek ismerősök kiválasztásra"; + +/* No comment provided by engineer. */ +"No contacts to add" = "Nincs hozzáadandó ismerős"; + +/* No comment provided by engineer. */ +"No delivery information" = "Nincs kézbesítési információ"; + +/* No comment provided by engineer. */ +"No device token!" = "Nincs eszköztoken!"; + +/* No comment provided by engineer. */ +"no e2e encryption" = "nincs e2e titkosítás"; + +/* No comment provided by engineer. */ +"No filtered chats" = "Nincsenek szűrt csevegések"; + +/* No comment provided by engineer. */ +"No group!" = "Csoport nem található!"; + +/* No comment provided by engineer. */ +"No history" = "Nincsenek előzmények"; + +/* No comment provided by engineer. */ +"No permission to record voice message" = "Nincs engedély a hangüzenet rögzítésére"; + +/* No comment provided by engineer. */ +"No received or sent files" = "Nincsenek fogadott vagy küldött fájlok"; + +/* copied message info in history */ +"no text" = "nincs szöveg"; + +/* No comment provided by engineer. */ +"Not compatible!" = "Nem kompatibilis!"; + +/* No comment provided by engineer. */ +"Notifications" = "Értesítések"; + +/* No comment provided by engineer. */ +"Notifications are disabled!" = "Az értesítések le vannak tiltva!"; + +/* No comment provided by engineer. */ +"Now admins can:\n- delete members' messages.\n- disable members (\"observer\" role)" = "Most már az adminok is:\n- törölhetik a tagok üzeneteit.\n- letilthatnak tagokat (\"megfigyelő\" szerepkör)"; + +/* member role */ +"observer" = "megfigyelő"; + +/* enabled status + group pref value + time to disappear */ +"off" = "ki"; + +/* No comment provided by engineer. */ +"Off" = "Ki"; + +/* feature offered item */ +"offered %@" = "%@ ajánlotta"; + +/* feature offered item */ +"offered %@: %@" = "ajánlotta %1$@: %2$@-kor"; + +/* No comment provided by engineer. */ +"Ok" = "Rendben"; + +/* No comment provided by engineer. */ +"OK" = "Rendben"; + +/* No comment provided by engineer. */ +"Old database" = "Régi adatbázis"; + +/* No comment provided by engineer. */ +"Old database archive" = "Régi adatbázis archívum"; + +/* group pref value */ +"on" = "be"; + +/* No comment provided by engineer. */ +"One-time invitation link" = "Egyszer használatos meghívó hivatkozás"; + +/* No comment provided by engineer. */ +"Onion hosts will be required for connection. Requires enabling VPN." = "A csatlakozáshoz Onion host-okra lesz szükség. VPN engedélyezése szükséges."; + +/* No comment provided by engineer. */ +"Onion hosts will be used when available. Requires enabling VPN." = "Onion host-ok használata, ha azok rendelkezésre állnak. VPN engedélyezése szükséges."; + +/* No comment provided by engineer. */ +"Onion hosts will not be used." = "Onion host-ok 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 a klienseszközök tárolják a felhasználói profilokat, névjegyeket, csoportokat és a **2 rétegű végponttól-végpontig titkosítással** küldött üzeneteket."; + +/* No comment provided by engineer. */ +"Only group owners can change group preferences." = "Csak a csoporttulajdonosok módosíthatják a csoportbeállításokat."; + +/* No comment provided by engineer. */ +"Only group owners can enable files and media." = "Csak a csoporttulajdonosok engedélyezhetik a fájlok- és a médiatartalmak küldését."; + +/* No comment provided by engineer. */ +"Only group owners can enable voice messages." = "Csak a csoporttulajdonosok engedélyezhetik a hangüzenetek küldését."; + +/* No comment provided by engineer. */ +"Only you can add message reactions." = "Csak ön adhat hozzá üzenetreakciókat."; + +/* No comment provided by engineer. */ +"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "Visszafordíthatatlanul csak ön törölhet üzeneteket (ismerőse csak törlésre jelölheti őket ). (24 óra)"; + +/* No comment provided by engineer. */ +"Only you can make calls." = "Csak ön tud hívásokat indítani."; + +/* No comment provided by engineer. */ +"Only you can send disappearing messages." = "Csak ön tud eltűnő üzeneteket küldeni."; + +/* No comment provided by engineer. */ +"Only you can send voice messages." = "Csak ön tud hangüzeneteket küldeni."; + +/* No comment provided by engineer. */ +"Only your contact can add message reactions." = "Csak az ismerős tud üzeneteakciókat adni."; + +/* No comment provided by engineer. */ +"Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" = "Csak az ismerős tud visszafordíthatatlanul törölni üzeneteket (megjelölheti őket törlésre). (24 óra)"; + +/* No comment provided by engineer. */ +"Only your contact can make calls." = "Csak az ismerős tud hívást indítani."; + +/* No comment provided by engineer. */ +"Only your contact can send disappearing messages." = "Csak az ismerős tud eltűnő üzeneteket küldeni."; + +/* No comment provided by engineer. */ +"Only your contact can send voice messages." = "Csak az ismerős tud hangüzeneteket küldeni."; + +/* No comment provided by engineer. */ +"Open" = "Megnyitás"; + +/* No comment provided by engineer. */ +"Open chat" = "Csevegés megnyitása"; + +/* authentication reason */ +"Open chat console" = "Csevegés konzol megnyitása"; + +/* No comment provided by engineer. */ +"Open group" = "Csoport megnyitása"; + +/* No comment provided by engineer. */ +"Open Settings" = "Beállítások megnyitása"; + +/* authentication reason */ +"Open user profiles" = "Felhasználói 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."; + +/* No comment provided by engineer. */ +"Opening app…" = "Az alkalmazás megnyitása…"; + +/* No comment provided by engineer. */ +"Or scan QR code" = "Vagy QR-kód beolvasása"; + +/* No comment provided by engineer. */ +"Or show this code" = "Vagy mutassa meg ezt a kódot"; + +/* member role */ +"owner" = "tulajdonos"; + +/* No comment provided by engineer. */ +"Passcode" = "Jelkód"; + +/* No comment provided by engineer. */ +"Passcode changed!" = "A jelkód megváltozott!"; + +/* No comment provided by engineer. */ +"Passcode entry" = "Jelkód bevitele"; + +/* No comment provided by engineer. */ +"Passcode not changed!" = "A jelkód nem változott!"; + +/* No comment provided by engineer. */ +"Passcode set!" = "A jelkód beállítva!"; + +/* No comment provided by engineer. */ +"Password to show" = "Jelszó mutatása"; + +/* past/unknown group member */ +"Past member %@" = "Korábbi csoport tag %@"; + +/* No comment provided by engineer. */ +"Paste desktop address" = "Számítógép azonosítójának beillesztése"; + +/* No comment provided by engineer. */ +"Paste image" = "Kép beillesztése"; + +/* No comment provided by engineer. */ +"Paste link to connect!" = "Hivatkozás beillesztése a csatlakozáshoz!"; + +/* No comment provided by engineer. */ +"Paste the link you received" = "Fogadott hivatkozás beillesztése"; + +/* No comment provided by engineer. */ +"peer-to-peer" = "ponttól-pontig"; + +/* No comment provided by engineer. */ +"People can connect to you only via the links you share." = "Az emberek csak az ön által megosztott hivatkozáson keresztül kapcsolódhatnak."; + +/* No comment provided by engineer. */ +"Periodically" = "Rendszeresen"; + +/* message decrypt error item */ +"Permanent decryption error" = "Végleges visszafejtési hiba"; + +/* No comment provided by engineer. */ +"PING count" = "PING számláló"; + +/* No comment provided by engineer. */ +"PING interval" = "PING időköze"; + +/* No comment provided by engineer. */ +"Please ask your contact to enable sending voice messages." = "Ismerős felkérése, hogy engedélyezze a hangüzenetek küldését."; + +/* No comment provided by engineer. */ +"Please check that you used the correct link or ask your contact to send you another one." = "Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg ismerősét, hogy küldjön egy másikat."; + +/* No comment provided by engineer. */ +"Please check your network connection with %@ and try again." = "Kérjük, ellenőrizze hálózati kapcsolatát a(z) %@ segítségével, és próbálja újra."; + +/* No comment provided by engineer. */ +"Please check yours and your contact preferences." = "Ellenőrizze az ön és ismerőse beállításait."; + +/* No comment provided by engineer. */ +"Please contact developers.\nError: %@" = "Lépjen kapcsolatba a fejlesztőkkel.\nHiba: %@"; + +/* No comment provided by engineer. */ +"Please contact group admin." = "Lépjen kapcsolatba a csoport adminnal."; + +/* No comment provided by engineer. */ +"Please enter correct current passphrase." = "Adja meg a helyes aktuális jelmondatát."; + +/* No comment provided by engineer. */ +"Please enter the previous password after restoring database backup. This action can not be undone." = "Előző jelszó megadása az adatbázis biztonsági mentésének visszaállítása után. Ez a művelet nem visszavonható."; + +/* No comment provided by engineer. */ +"Please remember or store it securely - there is no way to recover a lost passcode!" = "Jegyezze fel vagy tárolja el biztonságosan - az elveszett jelkódot nem lehet visszaállítani!"; + +/* No comment provided by engineer. */ +"Please report it to the developers." = "Jelentse a fejlesztőknek."; + +/* No comment provided by engineer. */ +"Please restart the app and migrate the database to enable push notifications." = "Indítsa újra az alkalmazást az adatbázis-migrációhoz szükséges push értesítések engedélyezéséhez."; + +/* No comment provided by engineer. */ +"Please store passphrase securely, you will NOT be able to access chat if you lose it." = "Tárolja el biztonságosan jelmondát, mert ha elveszti azt, akkor NEM férhet hozzá a csevegéshez."; + +/* No comment provided by engineer. */ +"Please store passphrase securely, you will NOT be able to change it if you lose it." = "Tárolja el biztonságosan jelmondatát, mert ha elveszíti azt, NEM tudja megváltoztatni."; + +/* No comment provided by engineer. */ +"Polish interface" = "Lengyel kezelőfelület"; + +/* server test error */ +"Possibly, certificate fingerprint in server address is incorrect" = "Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen"; + +/* No comment provided by engineer. */ +"Preserve the last message draft, with attachments." = "Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt."; + +/* No comment provided by engineer. */ +"Preset server" = "Előre beállított kiszolgáló"; + +/* No comment provided by engineer. */ +"Preset server address" = "Előre beállított kiszolgáló címe"; + +/* No comment provided by engineer. */ +"Preview" = "Előnézet"; + +/* No comment provided by engineer. */ +"Privacy & security" = "Adatvédelem és biztonság"; + +/* No comment provided by engineer. */ +"Privacy redefined" = "Adatvédelem újraértelmezve"; + +/* No comment provided by engineer. */ +"Private filenames" = "Privát fájl nevek"; + +/* name of notes to self */ +"Private notes" = "Privát jegyzetek"; + +/* No comment provided by engineer. */ +"Profile and server connections" = "Profil és kiszolgálókapcsolatok"; + +/* No comment provided by engineer. */ +"Profile image" = "Profilkép"; + +/* No comment provided by engineer. */ +"Profile name" = "Profilnév"; + +/* No comment provided by engineer. */ +"Profile name:" = "Profil neve:"; + +/* No comment provided by engineer. */ +"Profile password" = "Profiljelszó"; + +/* No comment provided by engineer. */ +"Profile update will be sent to your contacts." = "A profilfrissítés elküldésre került az ismerősök számára."; + +/* No comment provided by engineer. */ +"Prohibit audio/video calls." = "Hang- és videóhívások tiltása."; + +/* No comment provided by engineer. */ +"Prohibit irreversible message deletion." = "Az üzenetek véglegesen való törlése le van tiltva."; + +/* No comment provided by engineer. */ +"Prohibit message reactions." = "Üzenetreakciók tiltása."; + +/* No comment provided by engineer. */ +"Prohibit messages reactions." = "Az üzenetreakciók tiltása."; + +/* No comment provided by engineer. */ +"Prohibit sending direct messages to members." = "Közvetlen üzenetek küldésének letiltása tagok részére."; + +/* No comment provided by engineer. */ +"Prohibit sending disappearing messages." = "Eltűnő üzenetek küldésének letiltása."; + +/* No comment provided by engineer. */ +"Prohibit sending files and media." = "Fájlok- és a médiatartalom küldés letiltása."; + +/* No comment provided by engineer. */ +"Prohibit sending voice messages." = "Hangüzenetek küldésének letiltása."; + +/* No comment provided by engineer. */ +"Protect app screen" = "App képernyőjének védelme"; + +/* No comment provided by engineer. */ +"Protect your chat profiles with a password!" = "Csevegési profiljok védelme jelszóval!"; + +/* No comment provided by engineer. */ +"Protocol timeout" = "Protokoll időtúllépés"; + +/* No comment provided by engineer. */ +"Protocol timeout per KB" = "Protokoll időkorlát KB-onként"; + +/* No comment provided by engineer. */ +"Push notifications" = "Push értesítések"; + +/* No comment provided by engineer. */ +"Rate the app" = "Értékelje az alkalmazást"; + +/* chat item menu */ +"React…" = "Reagálj…"; + +/* No comment provided by engineer. */ +"Read" = "Olvasd el"; + +/* No comment provided by engineer. */ +"Read more" = "Tudjon meg többet"; + +/* No comment provided by engineer. */ +"Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "További információ a [Felhasználói útmutatóban](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)."; + +/* No comment provided by engineer. */ +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "További információ a [Felhasználói útmutatóban](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)."; + +/* No comment provided by engineer. */ +"Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "További információ a [Felhasználói útmutatóban](https://simplex.chat/docs/guide/readme.html#connect-to-friends)."; + +/* No comment provided by engineer. */ +"Read more in our [GitHub repository](https://github.com/simplex-chat/simplex-chat#readme)." = "További információ a [GitHub tárolóban](https://github.com/simplex-chat/simplex-chat#readme)."; + +/* No comment provided by engineer. */ +"Read more in our GitHub repository." = "További információ a GitHub tárolónkban."; + +/* No comment provided by engineer. */ +"Receipts are disabled" = "Üzenet kézbesítési jelentés letiltva"; + +/* No comment provided by engineer. */ +"received answer…" = "fogadott válasz…"; + +/* No comment provided by engineer. */ +"Received at" = "Fogadva ekkor"; + +/* copied message info */ +"Received at: %@" = "Fogadva ekkor: %@"; + +/* No comment provided by engineer. */ +"received confirmation…" = "visszaigazolás fogadása…"; + +/* notification */ +"Received file event" = "Fogadott fájl esemény"; + +/* message info title */ +"Received message" = "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."; + +/* No comment provided by engineer. */ +"Receiving file will be stopped." = "A fájl fogadása leállt."; + +/* No comment provided by engineer. */ +"Receiving via" = "Fogadás a"; + +/* No comment provided by engineer. */ +"Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." = "Legutóbbi előzmények és továbbfejlesztett [könyvtárbot] (simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2TxW3dfMfxy 3%23%2F%3Fv%3D1-2% 26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gloncbqjek4gloncbqjek."; + +/* No comment provided by engineer. */ +"Recipients see updates as you type them." = "A címzettek a beírás közben látják a frissítéseket."; + +/* No comment provided by engineer. */ +"Reconnect all connected servers to force message delivery. It uses additional traffic." = "Az összes csatlakoztatott kiszolgáló újrakapcsolása az üzenetek kézbesítésének kikényszerítéséhez. Ez további forgalmat használ."; + +/* No comment provided by engineer. */ +"Reconnect servers?" = "Kiszolgálók újracsatlakoztatása?"; + +/* No comment provided by engineer. */ +"Record updated at" = "A bejegyzés frissítve"; + +/* copied message info */ +"Record updated at: %@" = "A bejegyzés frissítve: %@"; + +/* No comment provided by engineer. */ +"Reduced battery usage" = "Csökkentett akkumulátorhasználat"; + +/* reject incoming call via notification */ +"Reject" = "Elutasítás"; + +/* No comment provided by engineer. */ +"Reject (sender NOT notified)" = "Elutasítás (a feladó NEM kap értesítést)"; + +/* No comment provided by engineer. */ +"Reject contact request" = "Kapcsolatfelvételi kérelem elutasítása"; + +/* call status */ +"rejected call" = "elutasított hívás"; + +/* No comment provided by engineer. */ +"Relay server is only used if necessary. Another party can observe your IP address." = "Az átjátszó kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címét."; + +/* No comment provided by engineer. */ +"Relay server protects your IP address, but it can observe the duration of the call." = "Az átjátszó kiszolgáló megvédi IP-címét, de megfigyelheti a hívás időtartamát."; + +/* No comment provided by engineer. */ +"Remove" = "Eltávolítás"; + +/* No comment provided by engineer. */ +"Remove member" = "Tag eltávolítása"; + +/* No comment provided by engineer. */ +"Remove member?" = "Tag eltávolítása?"; + +/* No comment provided by engineer. */ +"Remove passphrase from keychain?" = "Jelmondat eltávolítása a kulcstárolóból?"; + +/* No comment provided by engineer. */ +"removed" = "eltávolítva"; + +/* rcv group event chat item */ +"removed %@" = "%@ eltávolítva"; + +/* profile update event chat item */ +"removed contact address" = "törölt csatlakozási cím"; + +/* profile update event chat item */ +"removed profile picture" = "törölt profilkép"; + +/* rcv group event chat item */ +"removed you" = "eltávolítottak"; + +/* No comment provided by engineer. */ +"Renegotiate" = "Újraegyzetetés"; + +/* No comment provided by engineer. */ +"Renegotiate encryption" = "Titkosítás újraegyeztetése"; + +/* No comment provided by engineer. */ +"Renegotiate encryption?" = "Titkosítás újraegyeztetése?"; + +/* No comment provided by engineer. */ +"Repeat connection request?" = "Kapcsolódási kérés megismétlése?"; + +/* No comment provided by engineer. */ +"Repeat join request?" = "Csatlakozási kérés megismétlése?"; + +/* chat item action */ +"Reply" = "Válasz"; + +/* No comment provided by engineer. */ +"Required" = "Megkövetelt"; + +/* No comment provided by engineer. */ +"Reset" = "Alaphelyzetbe állítás"; + +/* No comment provided by engineer. */ +"Reset colors" = "Színek alaphelyzetbe állítása"; + +/* No comment provided by engineer. */ +"Reset to defaults" = "Alaphelyzetbe állítás"; + +/* No comment provided by engineer. */ +"Restart the app to create a new chat profile" = "Új csevegési profil létrehozásához indítsa újra az alkalmazást"; + +/* No comment provided by engineer. */ +"Restart the app to use imported chat database" = "Az importált csevegési adatbázis használatához indítsa újra az alkalmazást"; + +/* No comment provided by engineer. */ +"Restore" = "Visszaállítás"; + +/* No comment provided by engineer. */ +"Restore database backup" = "Adatbázismentés visszaállítása"; + +/* No comment provided by engineer. */ +"Restore database backup?" = "Adatbázismentés visszaállítása?"; + +/* No comment provided by engineer. */ +"Restore database error" = "Hiba az adatbázis visszaállításakor"; + +/* No comment provided by engineer. */ +"Retry" = "Újrapróbálkozás"; + +/* chat item action */ +"Reveal" = "Felfedés"; + +/* No comment provided by engineer. */ +"Revert" = "Visszaállít"; + +/* No comment provided by engineer. */ +"Revoke" = "Visszavonás"; + +/* cancel file action */ +"Revoke file" = "Fájl visszavonása"; + +/* No comment provided by engineer. */ +"Revoke file?" = "Fájl visszavonása?"; + +/* No comment provided by engineer. */ +"Role" = "Szerepkör"; + +/* No comment provided by engineer. */ +"Run chat" = "Csevegési szolgáltatás indítása"; + +/* chat item action */ +"Save" = "Mentés"; + +/* No comment provided by engineer. */ +"Save (and notify contacts)" = "Mentés (és az ismerősök értesítése)"; + +/* No comment provided by engineer. */ +"Save and notify contact" = "Mentés és ismerős értesítése"; + +/* No comment provided by engineer. */ +"Save and notify group members" = "Mentés és a csoporttagok értesítése"; + +/* No comment provided by engineer. */ +"Save and update group profile" = "Mentés és a csoport profil frissítése"; + +/* No comment provided by engineer. */ +"Save archive" = "Archívum mentése"; + +/* No comment provided by engineer. */ +"Save auto-accept settings" = "Automatikus elfogadási beállítások mentése"; + +/* No comment provided by engineer. */ +"Save group profile" = "Csoport profil elmentése"; + +/* No comment provided by engineer. */ +"Save passphrase and open chat" = "Jelmondat elmentése és csevegés megnyitása"; + +/* No comment provided by engineer. */ +"Save passphrase in Keychain" = "Jelmondat mentése a kulcstárban"; + +/* No comment provided by engineer. */ +"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"; + +/* No comment provided by engineer. */ +"Save servers" = "Kiszolgálók mentése"; + +/* No comment provided by engineer. */ +"Save servers?" = "Kiszolgálók mentése?"; + +/* No comment provided by engineer. */ +"Save settings?" = "Beállítások mentése?"; + +/* No comment provided by engineer. */ +"Save welcome message?" = "Üdvözlőszöveg mentése?"; + +/* message info title */ +"Saved message" = "Mentett üzenet"; + +/* No comment provided by engineer. */ +"Saved WebRTC ICE servers will be removed" = "A mentett WebRTC ICE kiszolgálók eltávolításra kerülnek"; + +/* No comment provided by engineer. */ +"Scan code" = "Kód beolvasása"; + +/* No comment provided by engineer. */ +"Scan QR code" = "QR-kód beolvasása"; + +/* No comment provided by engineer. */ +"Scan QR code from desktop" = "QR-kód beolvasása számítógépről"; + +/* No comment provided by engineer. */ +"Scan security code from your contact's app." = "Biztonsági kód beolvasása ismerős általi alkalmazásból."; + +/* No comment provided by engineer. */ +"Scan server QR code" = "A kiszolgáló QR-kódjának beolvasása"; + +/* No comment provided by engineer. */ +"Search" = "Keresés"; + +/* No comment provided by engineer. */ +"Search bar accepts invitation links." = "A keresősáv fogadja a meghívó hivatkozásokat."; + +/* No comment provided by engineer. */ +"Search or paste SimpleX link" = "Keresés, vagy SimpleX hivatkozás beillesztése"; + +/* network option */ +"sec" = "mp"; + +/* time unit */ +"seconds" = "másodperc"; + +/* No comment provided by engineer. */ +"secret" = "titok"; + +/* server test step */ +"Secure queue" = "Biztonságos várólista"; + +/* No comment provided by engineer. */ +"Security assessment" = "Biztonsági kiértékelés"; + +/* No comment provided by engineer. */ +"Security code" = "Biztonsági kód"; + +/* chat item text */ +"security code changed" = "biztonsági kód megváltozott"; + +/* No comment provided by engineer. */ +"Select" = "Választás"; + +/* No comment provided by engineer. */ +"Self-destruct" = "Önmegsemmisítés"; + +/* No comment provided by engineer. */ +"Self-destruct passcode" = "Önmegsemmisítési jelkód"; + +/* No comment provided by engineer. */ +"Self-destruct passcode changed!" = "Az önmegsemmisítési jelkód megváltozott!"; + +/* No comment provided by engineer. */ +"Self-destruct passcode enabled!" = "Az önmegsemmisítési jelkód engedélyezve!"; + +/* No comment provided by engineer. */ +"Send" = "Küldés"; + +/* No comment provided by engineer. */ +"Send a live message - it will update for the recipient(s) as you type it" = "Élő üzenet küldése - a címzett(ek) számára frissül, ahogy beírja"; + +/* No comment provided by engineer. */ +"Send delivery receipts to" = "A kézbesítési jelentéseket a következő címre kell küldeni"; + +/* No comment provided by engineer. */ +"send direct message" = "közvetlen üzenet küldése"; + +/* No comment provided by engineer. */ +"Send direct message" = "Közvetlen üzenet küldése"; + +/* No comment provided by engineer. */ +"Send direct message to connect" = "A kapcsolódáshoz közvetlen üzenet küldése"; + +/* No comment provided by engineer. */ +"Send disappearing message" = "Eltűnő üzenet küldése"; + +/* No comment provided by engineer. */ +"Send link previews" = "Hivatkozás előnézetek küldése"; + +/* No comment provided by engineer. */ +"Send live message" = "Élő üzenet küldése"; + +/* No comment provided by engineer. */ +"Send notifications" = "Értesítések küldése"; + +/* No comment provided by engineer. */ +"Send notifications:" = "Értesítések küldése:"; + +/* No comment provided by engineer. */ +"Send questions and ideas" = "Ötletek és kérdések beküldése"; + +/* No comment provided by engineer. */ +"Send receipts" = "Üzenet kézbesítési jelentések"; + +/* No comment provided by engineer. */ +"Send them from gallery or custom keyboards." = "Küldje el őket galériából vagy egyedi billentyűzetekről."; + +/* No comment provided by engineer. */ +"Send up to 100 last messages to new members." = "Utolsó 100 üzenet küldése új tagoknak."; + +/* No comment provided by engineer. */ +"Sender cancelled file transfer." = "A küldő megszakította a fájl átvitelt."; + +/* No comment provided by engineer. */ +"Sender may have deleted the connection request." = "A küldő törölhette a kapcsolódási kérelmet."; + +/* No comment provided by engineer. */ +"Sending delivery receipts will be enabled for all contacts in all visible chat profiles." = "A kézbesítési jelentések küldése engedélyezésre kerül az összes látható csevegési profilban lévő minden ismerős számára."; + +/* No comment provided by engineer. */ +"Sending delivery receipts will be enabled for all contacts." = "A kézbesítési jelentés küldése minden ismerős számára engedélyezésre kerül."; + +/* No comment provided by engineer. */ +"Sending file will be stopped." = "A fájl küldése leállt."; + +/* No comment provided by engineer. */ +"Sending receipts is disabled for %lld contacts" = "A kézbesítési jelentések küldése le van tiltva %lld ismerősnél"; + +/* No comment provided by engineer. */ +"Sending receipts is disabled for %lld groups" = "A kézbesítési jelentések küldése le van tiltva %lld csoportban"; + +/* No comment provided by engineer. */ +"Sending receipts is enabled for %lld contacts" = "A kézbesítési jelentések küldése engedélyezve van %lld ismerős számára"; + +/* No comment provided by engineer. */ +"Sending receipts is enabled for %lld groups" = "A kézbesítési jelentések küldése engedélyezve van %lld csoportban"; + +/* No comment provided by engineer. */ +"Sending via" = "Küldés ezen keresztül"; + +/* No comment provided by engineer. */ +"Sent at" = "Elküldve ekkor"; + +/* copied message info */ +"Sent at: %@" = "Elküldve ekkor: %@"; + +/* notification */ +"Sent file event" = "Elküldött fájl esemény"; + +/* message info title */ +"Sent message" = "Elküldött üzenet"; + +/* No comment provided by engineer. */ +"Sent messages will be deleted after set time." = "Az elküldött üzenetek törlésre kerülnek a beállított idő után."; + +/* server test error */ +"Server requires authorization to create queues, check password" = "A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát"; + +/* server test error */ +"Server requires authorization to upload, check password" = "A kiszolgálónak engedélyre van szüksége a várólisták feltöltéséhez, ellenőrizze jelszavát"; + +/* No comment provided by engineer. */ +"Server test failed!" = "A kiszolgáló tesztje sikertelen!"; + +/* No comment provided by engineer. */ +"Servers" = "Kiszolgálók"; + +/* No comment provided by engineer. */ +"Session code" = "Munkamenet kód"; + +/* No comment provided by engineer. */ +"Set 1 day" = "Beállítva 1 nap"; + +/* No comment provided by engineer. */ +"Set contact name…" = "Ismerős nevének beállítása…"; + +/* No comment provided by engineer. */ +"Set group preferences" = "Csoportbeállítások megadása"; + +/* No comment provided by engineer. */ +"Set it instead of system authentication." = "Rendszerhitelesítés helyetti beállítás."; + +/* profile update event chat item */ +"set new contact address" = "új kapcsolattartási cím beállítása"; + +/* profile update event chat item */ +"set new profile picture" = "új profilkép beállítása"; + +/* No comment provided by engineer. */ +"Set passcode" = "Jelkód beállítása"; + +/* No comment provided by engineer. */ +"Set passphrase to export" = "Jelmondat beállítása az exportáláshoz"; + +/* No comment provided by engineer. */ +"Set the message shown to new members!" = "Megjelenő üzenetet beállítása új tagok részére!"; + +/* No comment provided by engineer. */ +"Set timeouts for proxy/VPN" = "Időtúllépések beállítása a proxy/VPN számára"; + +/* No comment provided by engineer. */ +"Settings" = "Beállítások"; + +/* chat item action */ +"Share" = "Megosztás"; + +/* No comment provided by engineer. */ +"Share 1-time link" = "Egyszer használatos hivatkozás megosztása"; + +/* No comment provided by engineer. */ +"Share address" = "Azonosító megosztása"; + +/* No comment provided by engineer. */ +"Share address with contacts?" = "Megosztja az azonosítót az ismerősökkel?"; + +/* No comment provided by engineer. */ +"Share link" = "Hivatkozás megosztása"; + +/* No comment provided by engineer. */ +"Share this 1-time invite link" = "Egyszer használatos meghívó hivatkozás megosztása"; + +/* No comment provided by engineer. */ +"Share with contacts" = "Megosztás ismerősökkel"; + +/* No comment provided by engineer. */ +"Show calls in phone history" = "Hívások megjelenítése a híváslistában"; + +/* No comment provided by engineer. */ +"Show developer options" = "Fejlesztői beállítások mutatása"; + +/* No comment provided by engineer. */ +"Show last messages" = "Utolsó üzenetek megjelenítése"; + +/* No comment provided by engineer. */ +"Show preview" = "Előnézet megjelenítése"; + +/* No comment provided by engineer. */ +"Show:" = "Mutat:"; + +/* No comment provided by engineer. */ +"SimpleX address" = "SimpleX azonosító"; + +/* No comment provided by engineer. */ +"SimpleX Address" = "SimpleX azonosító"; + +/* No comment provided by engineer. */ +"SimpleX Chat security was audited by Trail of Bits." = "A SimpleX Chat biztonsága a Trail of Bits által lett auditálva."; + +/* simplex link type */ +"SimpleX contact address" = "SimpleX ismerős azonosítója"; + +/* notification */ +"SimpleX encrypted message or connection event" = "SimpleX titkosított üzenet vagy kapcsolati esemény"; + +/* simplex link type */ +"SimpleX group link" = "SimpleX csoport hivatkozás"; + +/* No comment provided by engineer. */ +"SimpleX links" = "SimpleX hivatkozások"; + +/* No comment provided by engineer. */ +"SimpleX Lock" = "SimpleX zárolás"; + +/* No comment provided by engineer. */ +"SimpleX Lock mode" = "SimpleX zárolási mód"; + +/* No comment provided by engineer. */ +"SimpleX Lock not enabled!" = "SimpleX zárolás nincs engedélyezve!"; + +/* No comment provided by engineer. */ +"SimpleX Lock turned on" = "SimpleX zárolás bekapcsolva"; + +/* simplex link type */ +"SimpleX one-time invitation" = "SimpleX egyszer használatos meghívó"; + +/* No comment provided by engineer. */ +"Simplified incognito mode" = "Egyszerűsített inkognító mód"; + +/* No comment provided by engineer. */ +"Skip" = "Kihagyás"; + +/* No comment provided by engineer. */ +"Skipped messages" = "Kihagyott üzenetek"; + +/* No comment provided by engineer. */ +"Small groups (max 20)" = "Kis csoportok (max. 20 tag)"; + +/* No comment provided by engineer. */ +"SMP servers" = "Üzenetküldő (SMP) kiszolgálók"; + +/* No comment provided by engineer. */ +"Some non-fatal errors occurred during import - you may see Chat console for more details." = "Néhány nem végzetes hiba történt az importálás során – további részletekért a csevegési konzolban olvashat."; + +/* notification title */ +"Somebody" = "Valaki"; + +/* No comment provided by engineer. */ +"Start chat" = "Csevegés indítása"; + +/* No comment provided by engineer. */ +"Start chat?" = "Csevegés indítása?"; + +/* No comment provided by engineer. */ +"Start migration" = "Migráció indítása"; + +/* No comment provided by engineer. */ +"starting…" = "indítás…"; + +/* No comment provided by engineer. */ +"Stop" = "Megállítás"; + +/* No comment provided by engineer. */ +"Stop chat to enable database actions" = "Csevegés leállítása az adatbázis-műveletek engedélyezéséhez"; + +/* No comment provided by engineer. */ +"Stop chat to export, import or delete chat database. You will not be able to receive and send messages while the chat is stopped." = "A csevegés leállítása a csevegőadatbázis exportálásához, importálásához vagy törléséhez. A csevegés leállítása alatt nem tud üzeneteket fogadni és küldeni."; + +/* No comment provided by engineer. */ +"Stop chat?" = "Csevegési szolgáltatás megállítása?"; + +/* cancel file action */ +"Stop file" = "Fájl megállítása"; + +/* No comment provided by engineer. */ +"Stop receiving file?" = "Fájl fogadás megszakítása?"; + +/* No comment provided by engineer. */ +"Stop sending file?" = "Fájl küldés megszakítása?"; + +/* No comment provided by engineer. */ +"Stop sharing" = "Megosztás leállítása"; + +/* No comment provided by engineer. */ +"Stop sharing address?" = "Címmegosztás megállítása?"; + +/* authentication reason */ +"Stop SimpleX" = "A SimpleX megállítása"; + +/* No comment provided by engineer. */ +"strike" = "áthúzott"; + +/* No comment provided by engineer. */ +"Submit" = "Elküldés"; + +/* No comment provided by engineer. */ +"Support SimpleX Chat" = "Támogassa a SimpleX Chatet"; + +/* No comment provided by engineer. */ +"System" = "Rendszer"; + +/* No comment provided by engineer. */ +"System authentication" = "Rendszerhitelesítés"; + +/* No comment provided by engineer. */ +"Take picture" = "Fotó készítése"; + +/* No comment provided by engineer. */ +"Tap button " = "Koppintson a gombra "; + +/* No comment provided by engineer. */ +"Tap to activate profile." = "A profil aktiválásához koppintson az ikonra."; + +/* No comment provided by engineer. */ +"Tap to Connect" = "Koppintson a csatlakozáshoz"; + +/* No comment provided by engineer. */ +"Tap to join" = "Koppintson a csatlakozáshoz"; + +/* No comment provided by engineer. */ +"Tap to join incognito" = "Koppintson az inkognitómódhoz való csatlakozáshoz"; + +/* No comment provided by engineer. */ +"Tap to paste link" = "Koppintson a hivatkozás beillesztéséhez"; + +/* No comment provided by engineer. */ +"Tap to scan" = "Koppintson a beolvasáshoz"; + +/* No comment provided by engineer. */ +"Tap to start a new chat" = "Koppintson az új csevegés indításához"; + +/* No comment provided by engineer. */ +"TCP connection timeout" = "TCP kapcsolat időtúllépés"; + +/* No comment provided by engineer. */ +"TCP_KEEPCNT" = "TCP_KEEPCNT"; + +/* No comment provided by engineer. */ +"TCP_KEEPIDLE" = "TCP_KEEPIDLE"; + +/* No comment provided by engineer. */ +"TCP_KEEPINTVL" = "TCP_KEEPINTVL"; + +/* server test failure */ +"Test failed at step %@." = "A teszt sikertelen volt a(z) %@ lépésnél."; + +/* No comment provided by engineer. */ +"Test server" = "Kiszolgáló tesztelése"; + +/* No comment provided by engineer. */ +"Test servers" = "Kiszolgálók tesztelése"; + +/* No comment provided by engineer. */ +"Tests failed!" = "Sikertelen tesztek!"; + +/* No comment provided by engineer. */ +"Thank you for installing SimpleX Chat!" = "Köszönjük, hogy telepítette a SimpleX Chatet!"; + +/* No comment provided by engineer. */ +"Thanks to the users – [contribute via Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Köszönet a felhasználóknak – [hozzájárulás a Weblate-en keresztül](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; + +/* No comment provided by engineer. */ +"Thanks to the users – contribute via Weblate!" = "Köszönet a felhasználóknak - hozzájárulás a Weblaten!"; + +/* No comment provided by engineer. */ +"The 1st platform without any user identifiers – private by design." = "Az első csevegési rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre."; + +/* No comment provided by engineer. */ +"The 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 kapcsolatfelvételi kéréseket kap – beállítások megnyitása az engedélyezéshez."; + +/* No comment provided by engineer. */ +"The attempt to change database passphrase was not completed." = "Az adatbázis jelmondatának megváltoztatására tett kísérlet nem fejeződött be."; + +/* No comment provided by engineer. */ +"The code you scanned is not a SimpleX link QR code." = "A beolvasott kód nem egy SimpleX hivatkozás QR-kód."; + +/* No comment provided by engineer. */ +"The connection you accepted will be cancelled!" = "Az ön által elfogadott kapcsolat megszakad!"; + +/* No comment provided by engineer. */ +"The contact you shared this link with will NOT be able to connect!" = "Ismerőse NEM fog tudni csatlakozni, akivel megosztotta ezt a hivatkozást!"; + +/* No comment provided by engineer. */ +"The created archive is available via app Settings / Database / Old database archive." = "A létrehozott archívum a Beállítások / Adatbázis / Régi adatbázis-archívum menüpontban érhető el."; + +/* No comment provided by engineer. */ +"The encryption is working and the new encryption agreement is not required. It may result in connection errors!" = "A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet!"; + +/* No comment provided by engineer. */ +"The hash of the previous message is different." = "Az előző üzenet hash-e más."; + +/* No comment provided by engineer. */ +"The ID of the next message is incorrect (less or equal to the previous).\nIt can happen because of some bug or when the connection is compromised." = "A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel).\nEz valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő."; + +/* No comment provided by engineer. */ +"The message will be deleted for all members." = "Az üzenet minden tag számára törlésre kerül."; + +/* No comment provided by engineer. */ +"The message will be marked as moderated for all members." = "Az üzenet minden tag számára moderáltként lesz megjelölve."; + +/* No comment provided by engineer. */ +"The next generation of private messaging" = "A privát üzenetküldés következő generációja"; + +/* No comment provided by engineer. */ +"The old database was not removed during the migration, it can be deleted." = "A régi adatbázis nem került eltávolításra a migráció során, így törölhető."; + +/* No comment provided by engineer. */ +"The profile is only shared with your contacts." = "Profilja csak az ismerősök számára kerül megosztásra."; + +/* No comment provided by engineer. */ +"The second tick we missed! ✅" = "A második jelölés, amit kihagytunk! ✅"; + +/* No comment provided by engineer. */ +"The sender will NOT be notified" = "A feladó NEM fog értesítést kapni"; + +/* No comment provided by engineer. */ +"The servers for new connections of your current chat profile **%@**." = "Jelenlegi profil új ismerőseinek kiszolgálói **%@**."; + +/* No comment provided by engineer. */ +"The text you pasted is not a SimpleX link." = "A beillesztett szöveg nem egy SimpleX hivatkozás."; + +/* No comment provided by engineer. */ +"Theme" = "Téma"; + +/* No comment provided by engineer. */ +"These settings are for your current profile **%@**." = "Ezek a beállítások a jelenlegi **%@** profiljára vonatkoznak."; + +/* No comment provided by engineer. */ +"They can be overridden in contact and group settings." = "Ezek felülbírálhatóak az ismerős- és csoportbeállításokban."; + +/* No comment provided by engineer. */ +"This action cannot be undone - all received and sent files and media will be deleted. Low resolution pictures will remain." = "Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalommal együtt törlésre kerülnek. Az alacsony felbontású fotók viszont megmaradnak."; + +/* No comment provided by engineer. */ +"This action cannot be undone - the messages sent and received earlier than selected will be deleted. It may take several minutes." = "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."; + +/* No comment provided by engineer. */ +"This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Ez a művelet nem vonható vissza - profilok, ismerősök, üzenetek és fájlok visszafordíthatatlanul törlésre kerülnek."; + +/* notification title */ +"this contact" = "ez az ismerős"; + +/* No comment provided by engineer. */ +"This device name" = "Ennek az eszköznek a neve"; + +/* No comment provided by engineer. */ +"This display name is invalid. Please choose another name." = "Ez a megjelenített felhasználónév érvénytelen. Válasszon egy másik nevet."; + +/* No comment provided by engineer. */ +"This group has over %lld members, delivery receipts are not sent." = "Ennek a csoportnak több mint %lld tagja van, a kézbesítési jelentések nem kerülnek elküldésre."; + +/* No comment provided by engineer. */ +"This group no longer exists." = "Ez a csoport már nem létezik."; + +/* No comment provided by engineer. */ +"This is your own one-time link!" = "Ez az egyszer használatos hivatkozása!"; + +/* No comment provided by engineer. */ +"This is your own SimpleX address!" = "Ez a SimpleX azonosítója!"; + +/* No comment provided by engineer. */ +"This setting applies to messages in your current chat profile **%@**." = "Ez a beállítás a jelenlegi **%@** profiljában lévő üzenetekre érvényes."; + +/* No comment provided by engineer. */ +"To ask any questions and to receive updates:" = "Bármilyen kérdés feltevéséhez és a frissítésekért:"; + +/* No comment provided by engineer. */ +"To connect, your contact can scan QR code or use the link in the app." = "A csatlakozáshoz az ismerős beolvashatja a QR-kódot, vagy használhatja az alkalmazásban található hivatkozást."; + +/* No comment provided by engineer. */ +"To hide unwanted messages." = "Kéretlen üzenetek elrejtése."; + +/* No comment provided by engineer. */ +"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 üzenetsorokhoz rendel azonosítókat, minden egyes ismerőshöz 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."; + +/* No comment provided by engineer. */ +"To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "Az adatavédelem érdekében kapcsolja be a SimpleX zárolás funkciót.\nA funkció engedélyezése előtt a rendszer felszólítja a hitelesítés befejezésére."; + +/* No comment provided by engineer. */ +"To record voice message please grant permission to use Microphone." = "Hangüzenet rögzítéséhez adjon engedélyt a mikrofon használathoz."; + +/* No comment provided by engineer. */ +"To reveal your hidden profile, enter a full password into a search field in **Your chat profiles** page." = "Rejtett profilja feltárásához írja be a teljes jelszót a keresőmezőbe a **Csevegési profiljai** oldalon."; + +/* No comment provided by engineer. */ +"To support instant push notifications the chat database has to be migrated." = "Az azonnali push értesítések támogatásához a csevegési adatbázis migrálása szükséges."; + +/* No comment provided by engineer. */ +"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "A végpontok közötti titkosítás ellenőrzéséhez ismerősével hasonlítsa össze (vagy szkennelje be) az eszközén lévő kódot."; + +/* No comment provided by engineer. */ +"Toggle incognito when connecting." = "Inkognító mód csatlakozáskor."; + +/* No comment provided by engineer. */ +"Transport isolation" = "Kapcsolat izolációs mód"; + +/* No comment provided by engineer. */ +"Trying to connect to the server used to receive messages from this contact (error: %@)." = "Csatlakozási kísérlet a kapcsolat üzeneteinek fogadására használt kiszolgálóhoz (hiba: %@)."; + +/* No comment provided by engineer. */ +"Trying to connect to the server used to receive messages from this contact." = "Csatlakozási kísérlet a kapcsolat üzeneteinek fogadására használt kiszolgálóhoz ettől az ismerőstől."; + +/* No comment provided by engineer. */ +"Turkish interface" = "Török kezelőfelület"; + +/* No comment provided by engineer. */ +"Turn off" = "Kikapcsolás"; + +/* No comment provided by engineer. */ +"Turn on" = "Bekapcsolás"; + +/* No comment provided by engineer. */ +"Unable to record voice message" = "Hangüzenet rögzítése nem lehetséges"; + +/* No comment provided by engineer. */ +"Unblock" = "Feloldás"; + +/* No comment provided by engineer. */ +"Unblock for all" = "Letiltás feloldása mindenki számára"; + +/* No comment provided by engineer. */ +"Unblock member" = "Tag feloldása"; + +/* No comment provided by engineer. */ +"Unblock member for all?" = "Mindenki számára feloldja a tag letiltását?"; + +/* No comment provided by engineer. */ +"Unblock member?" = "Tag feloldása?"; + +/* rcv group event chat item */ +"unblocked %@" = "%@ feloldva"; + +/* item status description */ +"Unexpected error: %@" = "Váratlan hiba: %@"; + +/* No comment provided by engineer. */ +"Unexpected migration state" = "Váratlan migrációs állapot"; + +/* No comment provided by engineer. */ +"Unfav." = "Nem kedvelt."; + +/* No comment provided by engineer. */ +"Unhide" = "Felfedés"; + +/* No comment provided by engineer. */ +"Unhide chat profile" = "Csevegési profil felfedése"; + +/* No comment provided by engineer. */ +"Unhide profile" = "Profil felfedése"; + +/* No comment provided by engineer. */ +"Unit" = "Egység"; + +/* connection info */ +"unknown" = "ismeretlen"; + +/* callkit banner */ +"Unknown caller" = "Ismeretlen hívó"; + +/* No comment provided by engineer. */ +"Unknown database error: %@" = "Ismeretlen adatbázishiba: %@"; + +/* No comment provided by engineer. */ +"Unknown error" = "Ismeretlen hiba"; + +/* No comment provided by engineer. */ +"unknown status" = "ismeretlen státusz"; + +/* No comment provided by engineer. */ +"Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "Hacsak nem az iOS hívási felületét használja, engedélyezze a Ne zavarjanak módot a megszakítások elkerülése érdekében."; + +/* No comment provided by engineer. */ +"Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "Hacsak az ismerős nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, hiba lehet – kérjük, jelentse.\nA csatlakozáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsolati hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e."; + +/* No comment provided by engineer. */ +"Unlink" = "Szétkapcsolás"; + +/* No comment provided by engineer. */ +"Unlink desktop?" = "Számítógép szétkapcsolása?"; + +/* No comment provided by engineer. */ +"Unlock" = "Feloldás"; + +/* authentication reason */ +"Unlock app" = "Alkalmazás feloldása"; + +/* No comment provided by engineer. */ +"Unmute" = "Némítás feloldása"; + +/* No comment provided by engineer. */ +"Unread" = "Olvasatlan"; + +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new members." = "Legfeljebb az utolsó 100 üzenet kerül elküldésre az új tagoknak."; + +/* No comment provided by engineer. */ +"Update" = "Frissítés"; + +/* No comment provided by engineer. */ +"Update .onion hosts setting?" = "Tor .onion host beállítások frissítése?"; + +/* No comment provided by engineer. */ +"Update database passphrase" = "Adatbázis jelmondat megváltoztatása"; + +/* No comment provided by engineer. */ +"Update network settings?" = "Hálózati beállítások megváltoztatása?"; + +/* No comment provided by engineer. */ +"Update transport isolation mode?" = "Kapcsolat izolációs mód frissítése?"; + +/* rcv group event chat item */ +"updated group profile" = "módosított csoport profil"; + +/* profile update event chat item */ +"updated profile" = "frissített profil"; + +/* No comment provided by engineer. */ +"Updating settings will re-connect the client to all servers." = "A beállítások frissítése a szerverekhez újra kapcsolódással jár."; + +/* No comment provided by engineer. */ +"Updating this setting will re-connect the client to all servers." = "A beállítás frissítésével a kliens újracsatlakozik az összes kiszolgálóhoz."; + +/* No comment provided by engineer. */ +"Upgrade and open chat" = "A csevegés frissítése és megnyitása"; + +/* server test step */ +"Upload file" = "Fájl feltöltése"; + +/* No comment provided by engineer. */ +"Use .onion hosts" = "Tor .onion hostok használata"; + +/* No comment provided by engineer. */ +"Use chat" = "Csevegés használata"; + +/* No comment provided by engineer. */ +"Use current profile" = "Jelenlegi profil használata"; + +/* No comment provided by engineer. */ +"Use for new connections" = "Alkalmazás új kapcsolatokhoz"; + +/* No comment provided by engineer. */ +"Use from desktop" = "Használat számítógépről"; + +/* No comment provided by engineer. */ +"Use iOS call interface" = "Az iOS hívófelület használata"; + +/* No comment provided by engineer. */ +"Use new incognito profile" = "Az új inkognító profil használata"; + +/* No comment provided by engineer. */ +"Use only local notifications?" = "Csak helyi értesítések használata?"; + +/* No comment provided by engineer. */ +"Use server" = "Kiszolgáló használata"; + +/* No comment provided by engineer. */ +"Use SimpleX Chat servers?" = "SimpleX Chat kiszolgálók használata?"; + +/* No comment provided by engineer. */ +"User profile" = "Felhasználói profil"; + +/* No comment provided by engineer. */ +"Using .onion hosts requires compatible VPN provider." = "A .onion hosztok használatához kompatibilis VPN szolgáltatóra van szükség."; + +/* No comment provided by engineer. */ +"Using SimpleX Chat servers." = "SimpleX Chat kiszolgálók használatban."; + +/* No comment provided by engineer. */ +"v%@" = "v%@"; + +/* No comment provided by engineer. */ +"v%@ (%@)" = "v%@ (%@)"; + +/* No comment provided by engineer. */ +"Verify code with desktop" = "Kód ellenőrzése a számítógépen"; + +/* No comment provided by engineer. */ +"Verify connection" = "Kapcsolat ellenőrzése"; + +/* No comment provided by engineer. */ +"Verify connection security" = "Kapcsolat biztonságának ellenőrzése"; + +/* No comment provided by engineer. */ +"Verify connections" = "Kapcsolatok ellenőrzése"; + +/* No comment provided by engineer. */ +"Verify security code" = "Biztonsági kód ellenőrzése"; + +/* No comment provided by engineer. */ +"Via browser" = "Böngészőn keresztül"; + +/* chat list item description */ +"via contact address link" = "ismerős azonosítójának hivatkozásán keresztül"; + +/* chat list item description */ +"via group link" = "csoport hivatkozáson keresztül"; + +/* chat list item description */ +"via one-time link" = "egyszer használatos hivatkozáson keresztül"; + +/* No comment provided by engineer. */ +"via relay" = "átjátszón keresztül"; + +/* No comment provided by engineer. */ +"Via secure quantum resistant protocol." = "Biztonságos kvantum ellenálló protokoll által."; + +/* No comment provided by engineer. */ +"Video call" = "Videóhívás"; + +/* No comment provided by engineer. */ +"video call (not e2e encrypted)" = "videóhívás (nem e2e titkosított)"; + +/* No comment provided by engineer. */ +"Video will be received when your contact completes uploading it." = "A videó akkor érkezik meg, amikor az ismerőse befejezte annak feltöltését."; + +/* No comment provided by engineer. */ +"Video will be received when your contact is online, please wait or check later!" = "A videó akkor érkezik meg, amikor az ismerős elérhető, várjon, vagy ellenőrizze később!"; + +/* No comment provided by engineer. */ +"Videos and files up to 1gb" = "Videók és fájlok 1Gb méretig"; + +/* No comment provided by engineer. */ +"View security code" = "Biztonsági kód megtekintése"; + +/* chat feature */ +"Visible history" = "Látható előzmények"; + +/* No comment provided by engineer. */ +"Voice message…" = "Hangüzenet…"; + +/* chat feature */ +"Voice messages" = "Hangüzenetek"; + +/* No comment provided by engineer. */ +"Voice messages are prohibited in this chat." = "A hangüzenetek le vannak tiltva ebben a csevegésben."; + +/* No comment provided by engineer. */ +"Voice messages are prohibited in this group." = "A hangüzenetek küldése le van tiltva ebben a csoportban."; + +/* No comment provided by engineer. */ +"Voice messages prohibited!" = "A hangüzenetek le vannak tilva!"; + +/* No comment provided by engineer. */ +"waiting for answer…" = "várakozás válaszra…"; + +/* No comment provided by engineer. */ +"waiting for confirmation…" = "várakozás a visszaigazolásra…"; + +/* No comment provided by engineer. */ +"Waiting for desktop..." = "Várakozás az asztali kliensre..."; + +/* No comment provided by engineer. */ +"Waiting for file" = "Fájlra várakozás"; + +/* No comment provided by engineer. */ +"Waiting for image" = "Képre várakozás"; + +/* No comment provided by engineer. */ +"Waiting for video" = "Videóra várakozás"; + +/* No comment provided by engineer. */ +"wants to connect to you!" = "kapcsolatba akar lépni önnel!"; + +/* No comment provided by engineer. */ +"Warning: you may lose some data!" = "Figyelmeztetés: néhány adat elveszhet!"; + +/* No comment provided by engineer. */ +"WebRTC ICE servers" = "WebRTC ICE kiszolgálók"; + +/* time unit */ +"weeks" = "hét"; + +/* No comment provided by engineer. */ +"Welcome %@!" = "Üdvözöllek %@!"; + +/* No comment provided by engineer. */ +"Welcome message" = "Üdvözlő üzenet"; + +/* No comment provided by engineer. */ +"What's new" = "Milyen újdonságok vannak"; + +/* No comment provided by engineer. */ +"When available" = "Amikor elérhető"; + +/* No comment provided by engineer. */ +"When people request to connect, you can accept or reject it." = "Csatlakozási kérelmek esetében, elfogadhatja vagy elutasíthatja azokat."; + +/* No comment provided by engineer. */ +"When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott."; + +/* No comment provided by engineer. */ +"With encrypted files and media." = "Titkosított fájlokkal és médiatartalommal."; + +/* No comment provided by engineer. */ +"With optional welcome message." = "Opcionális üdvözlő üzenettel."; + +/* No comment provided by engineer. */ +"With reduced battery usage." = "Csökkentett akkumulátorhasználattal."; + +/* No comment provided by engineer. */ +"Wrong database passphrase" = "Téves adatbázis jelmondat"; + +/* No comment provided by engineer. */ +"Wrong passphrase!" = "Téves jelmondat!"; + +/* No comment provided by engineer. */ +"XFTP servers" = "XFTP kiszolgálók"; + +/* pref value */ +"yes" = "igen"; + +/* No comment provided by engineer. */ +"You" = "Ön"; + +/* No comment provided by engineer. */ +"You accepted connection" = "Kapcsolódás elfogadva"; + +/* No comment provided by engineer. */ +"You allow" = "Engedélyezte"; + +/* No comment provided by engineer. */ +"You already have a chat profile with the same display name. Please choose another name." = "Már van egy csevegési profil ugyanezzel a megjelenített névvel. Válasszon egy másik nevet."; + +/* No comment provided by engineer. */ +"You are already connected to %@." = "Már csatlakozva van ehhez: %@."; + +/* No comment provided by engineer. */ +"You are already connecting to %@." = "Már folyamatban van a csatlakozás ehhez: %@."; + +/* No comment provided by engineer. */ +"You are already connecting via this one-time link!" = "Már csatlakozik ezen az egyszer használatos hivatkozáson keresztül!"; + +/* No comment provided by engineer. */ +"You are already in group %@." = "Már a %@ csoportban van."; + +/* No comment provided by engineer. */ +"You are already joining the group %@." = "Már folyamatban van a csatlakozás a csoporthoz %@."; + +/* No comment provided by engineer. */ +"You are already joining the group via this link!" = "Már csatlakozott a csoporthoz ezen a hivatkozáson keresztül!"; + +/* No comment provided by engineer. */ +"You are already joining the group via this link." = "Ezen a hivatkozáson keresztül már csatlakozik a csoporthoz."; + +/* No comment provided by engineer. */ +"You are already joining the group!\nRepeat join request?" = "Csatlakozás folyamatban!\nCsatlakozási kérés megismétlése?"; + +/* No comment provided by engineer. */ +"You are connected to the server used to receive messages from this contact." = "Kiszolgálóhoz történő csatlakozás, mely az adott ismerőstől érkező üzenetek fogadására szolgál."; + +/* No comment provided by engineer. */ +"you are invited to group" = "meghívást kapott a csoportba"; + +/* No comment provided by engineer. */ +"You are invited to group" = "Meghívást kapott a csoportba"; + +/* No comment provided by engineer. */ +"you are observer" = "megfigyelő szerep"; + +/* snd group event chat item */ +"you blocked %@" = "blokkolta őt: %@"; + +/* No comment provided by engineer. */ +"You can accept calls from lock screen, without device and app authentication." = "Hívásokat fogadhat a lezárási képernyőről, eszköz- és alkalmazáshitelesítés nélkül."; + +/* No comment provided by engineer. */ +"You can create it later" = "Létrehozás később"; + +/* No comment provided by engineer. */ +"You can enable later via Settings" = "Később engedélyezheti a Beállításokban"; + +/* 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üpontban."; + +/* No comment provided by engineer. */ +"You can hide or mute a user profile - swipe it to the right." = "Elrejthet vagy némíthat egy felhasználói profilt – csúsztasson jobbra."; + +/* No comment provided by engineer. */ +"You can make it visible to your SimpleX contacts via Settings." = "Láthatóvá teheti SimpleX ismerősök számára a Beállításokban."; + +/* notification body */ +"You can now send messages to %@" = "Mostantól küldhet üzeneteket %@ számára"; + +/* No comment provided by engineer. */ +"You can set lock screen notification preview via settings." = "A beállításokon keresztül beállíthatja a lezárási képernyő értesítési előnézetét."; + +/* No comment provided by engineer. */ +"You can share a link or a QR code - anybody will be able to join the group. You won't lose members of the group if you later delete it." = "Megoszthat egy hivatkozást vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Ha a csoport később törlésre kerül, akkor nem fogja elveszíteni annak tagjait."; + +/* No comment provided by engineer. */ +"You can share this address with your contacts to let them connect with **%@**." = "Megoszthatja ezt a hivatkozást ismerőseivel, hogy kapcsolatba léphessenek önnel a **%@** nevű profilján keresztül."; + +/* No comment provided by engineer. */ +"You can share your address as a link or QR code - anybody can connect to you." = "Megoszthatja azonosítóját hivatkozásként vagy QR-kódként – így bárki csatlakozhat önhöz."; + +/* No comment provided by engineer. */ +"You can start chat via app Settings / Database or by restarting the app" = "A csevegést az alkalmazás Beállítások / Adatbázis menü segítségével vagy az alkalmazás újraindításával indíthatja el"; + +/* No comment provided by engineer. */ +"You can turn on SimpleX Lock via Settings." = "A SimpleX zárolás a Beállításokon keresztül kapcsolható be."; + +/* No comment provided by engineer. */ +"You can use markdown to format messages:" = "Üzenetek formázása a szövegbe szúrt speciális karakterekkel:"; + +/* No comment provided by engineer. */ +"You can view invitation link again in connection details." = "A meghívó hivatkozást újra megtekintheti a kapcsolat részleteinél."; + +/* No comment provided by engineer. */ +"You can't send messages!" = "Nem lehet üzeneteket küldeni!"; + +/* chat item text */ +"you changed address" = "azonosítója megváltoztatva"; + +/* chat item text */ +"you changed address for %@" = "%@ azonosítója megváltoztatva"; + +/* snd group event chat item */ +"you changed role for yourself to %@" = "saját szerepkör megváltoztatva erre: %@"; + +/* snd group event chat item */ +"you changed role of %@ to %@" = "megváltoztatta %1$@ szerepkörét erre: %@"; + +/* No comment provided by engineer. */ +"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Ön szabályozhatja, hogy mely kiszogál(ók)ón keresztül **kapja** az üzeneteket, az ismerősöket - az üzenetküldéshez használt szervereken."; + +/* No comment provided by engineer. */ +"You could not be verified; please try again." = "Nem lehetett ellenőrizni; próbálja meg újra."; + +/* No comment provided by engineer. */ +"You have already requested connection via this address!" = "Már kért egy csatlakozást ezen az azonosítón keresztül!"; + +/* No comment provided by engineer. */ +"You have already requested connection!\nRepeat connection request?" = "Már kérelmezte a csatlakozást!\nKapcsolódási kérés megismétlése?"; + +/* No comment provided by engineer. */ +"You have no chats" = "Nincsenek csevegési üzenetek"; + +/* No comment provided by engineer. */ +"You have to enter passphrase every time the app starts - it is not stored on the device." = "A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul - nem az eszközön kerül tárolásra."; + +/* No comment provided by engineer. */ +"You invited a contact" = "Meghívott egy ismerőst"; + +/* No comment provided by engineer. */ +"You joined this group" = "Csatlakozott ehhez a csoporthoz"; + +/* No comment provided by engineer. */ +"You joined this group. Connecting to inviting group member." = "Csatlakozott ehhez a csoporthoz. Kapcsolódás a meghívó csoporttaghoz."; + +/* snd group event chat item */ +"you left" = "elhagyta"; + +/* No comment provided by engineer. */ +"You must use the most recent version of your chat database on one device ONLY, otherwise you may stop receiving the messages from some contacts." = "A csevegési adatbázis legfrissebb verzióját CSAK egy eszközön kell használnia, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogja megkapni valamennyi ismerőstől."; + +/* No comment provided by engineer. */ +"You need to allow your contact to send voice messages to be able to send them." = "Hangüzeneteket küldéséhez engedélyeznie kell azok küldését az ismerősök számára."; + +/* No comment provided by engineer. */ +"You rejected group invitation" = "Csoport meghívó elutasítva"; + +/* snd group event chat item */ +"you removed %@" = "eltávolította őt: %@"; + +/* No comment provided by engineer. */ +"You sent group invitation" = "Csoport meghívó elküldve"; + +/* chat list item description */ +"you shared one-time link" = "egyszer használatos hivatkozást osztott meg"; + +/* chat list item description */ +"you shared one-time link incognito" = "egyszer használatos hivatkozást osztott meg inkognitóban"; + +/* snd group event chat item */ +"you unblocked %@" = "feloldotta %@ blokkolását"; + +/* No comment provided by engineer. */ +"You will be connected to group when the group host's device is online, please wait or check later!" = "Akkor tud csatlakozni a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!"; + +/* No comment provided by engineer. */ +"You will be connected when group link host's device is online, please wait or check later!" = "Akkor lesz csatlakoztatva, amikor a csoportos hivatkozás tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!"; + +/* No comment provided by engineer. */ +"You will be connected when your connection request is accepted, please wait or check later!" = "Akkor lesz csatlakoztatva, ha a csatlakozási kérelme elfogadásra került, várjon, vagy ellenőrizze később!"; + +/* No comment provided by engineer. */ +"You will be connected when your contact's device is online, please wait or check later!" = "Akkor csatlakozik, amikor az ismerős eszköze online lesz, várjon, vagy ellenőrizze később!"; + +/* No comment provided by engineer. */ +"You will be required to authenticate when you start or resume the app after 30 seconds in background." = "Az alkalmazás indításakor, vagy 30 másodpercnyi háttérben töltött idő után az alkalmazáshoz visszatérve hitelesítés szükséges."; + +/* No comment provided by engineer. */ +"You will connect to all group members." = "Csatlakozni fog a csoport összes tagjához."; + +/* No comment provided by engineer. */ +"You will still receive calls and notifications from muted profiles when they are active." = "Továbbra is kap hívásokat és értesítéseket a némított profiloktól, ha azok aktívak."; + +/* No comment provided by engineer. */ +"You will stop receiving messages from this group. Chat history will be preserved." = "Ettől a csoporttól nem fog értesítéseket kapni. A csevegési előzmények megmaradnak."; + +/* No comment provided by engineer. */ +"You won't lose your contacts if you later delete your address." = "Nem veszíti el ismerőseit, ha később törli az azonosítóját."; + +/* No comment provided by engineer. */ +"you: " = "ön: "; + +/* No comment provided by engineer. */ +"You're trying to invite contact with whom you've shared an incognito profile to the group in which you're using your main profile" = "Egy olyan ismerőst próbál meghívni, akivel inkognító profilt osztott meg abban a csoportban, amelyben saját fő profilja van használatban"; + +/* No comment provided by engineer. */ +"You're using an incognito profile for this group - to prevent sharing your main profile inviting contacts is not allowed" = "Inkognító profilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében meghívók küldése tiltott"; + +/* No comment provided by engineer. */ +"Your %@ servers" = "%@ nevű profiljához tartozó kiszolgálók"; + +/* No comment provided by engineer. */ +"Your calls" = "Hívások"; + +/* No comment provided by engineer. */ +"Your chat database" = "Csevegési adatbázisa"; + +/* No comment provided by engineer. */ +"Your chat database is not encrypted - set passphrase to encrypt it." = "Csevegési adatbázisa nincs titkosítva – adjon meg egy jelmondatot a titkosításhoz."; + +/* No comment provided by engineer. */ +"Your chat profiles" = "Csevegési profiljai"; + +/* No comment provided by engineer. */ +"Your contact needs to be online for the connection to complete.\nYou can cancel this connection and remove the contact (and try later with a new link)." = "Az ismerősnek online kell lennie ahhoz, hogy a kapcsolat létrejöjjön.\nMegszakíthatja ezt a kapcsolatfelvételt és törölheti az ismerőst (ezt később ismét megpróbálhatja egy új hivatkozással)."; + +/* No comment provided by engineer. */ +"Your contact sent a file that is larger than currently supported maximum size (%@)." = "Ismerőse olyan fájlt küldött, amely meghaladja a jelenleg támogatott maximális méretet (%@)."; + +/* No comment provided by engineer. */ +"Your contacts can allow full message deletion." = "Ismerősök engedélyezhetik a teljes üzenet törlést."; + +/* No comment provided by engineer. */ +"Your contacts will remain connected." = "Az ismerősök továbbra is csatlakoztatva maradnak."; + +/* No comment provided by engineer. */ +"Your current chat database will be DELETED and REPLACED with the imported one." = "A jelenlegi csevegési adatbázis TÖRLŐDNI FOG, és a HELYÉRE az importált adatbázis kerül."; + +/* No comment provided by engineer. */ +"Your current profile" = "Jelenlegi profil"; + +/* No comment provided by engineer. */ +"Your ICE servers" = "ICE kiszolgálók"; + +/* No comment provided by engineer. */ +"Your preferences" = "Beállítások"; + +/* No comment provided by engineer. */ +"Your privacy" = "Adatvédelem"; + +/* No comment provided by engineer. */ +"Your profile" = "Profil"; + +/* No comment provided by engineer. */ +"Your profile **%@** will be shared." = "**%@** nevű profilja megosztásra kerül."; + +/* No comment provided by engineer. */ +"Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Profilja az eszközön van tárolva, és csak az ismerősökkel kerül megosztásra.\nA SimpleX kiszolgálók nem látjhatják profilját."; + +/* No comment provided by engineer. */ +"Your profile, contacts and delivered messages are stored on your device." = "Profilja, ismerősök és az elküldött üzenetek az eszközön kerülnek tárolásra."; + +/* No comment provided by engineer. */ +"Your random profile" = "Véletlenszerű profil"; + +/* No comment provided by engineer. */ +"Your server" = "Saját kiszolgáló"; + +/* No comment provided by engineer. */ +"Your server address" = "Saját kiszolgáló cím"; + +/* No comment provided by engineer. */ +"Your settings" = "Beállítások"; + +/* No comment provided by engineer. */ +"Your SimpleX address" = "SimpleX azonosítója"; + +/* No comment provided by engineer. */ +"Your SMP servers" = "SMP kiszolgálók"; + +/* No comment provided by engineer. */ +"Your XFTP servers" = "XFTP kiszolgálók"; + diff --git a/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings b/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings new file mode 100644 index 0000000000..8ba8ca5b41 --- /dev/null +++ b/apps/ios/hu.lproj/SimpleX--iOS--InfoPlist.strings @@ -0,0 +1,18 @@ +/* Bundle name */ +"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 csatlakozhasson 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"; + +/* Privacy - Local Network Usage Description */ +"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."; + +/* 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"; + diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index 85808267e6..88b3497758 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Il più privato**: non usare il server di notifica di SimpleX Chat, controlla i messaggi periodicamente in secondo piano (dipende da quanto spesso usi l'app)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Nota bene**: usare lo stesso database su due dispositivi bloccherà la decifrazione dei messaggi dalle tue connessioni, come misura di sicurezza."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Nota bene**: NON potrai recuperare o cambiare la password se la perdi."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Attenzione**: le notifiche push istantanee richiedono una password salvata nel portachiavi."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Attenzione**: l'archivio verrà rimosso."; + /* No comment provided by engineer. */ "*bold*" = "\\*grassetto*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ si è connesso/a"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ scaricati"; + /* notification title */ "%@ is connected!" = "%@ è connesso/a!"; @@ -148,6 +157,9 @@ /* No comment provided by engineer. */ "%@ servers" = "Server %@"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ caricati"; + /* notification title */ "%@ wants to connect!" = "%@ si vuole connettere!"; @@ -377,6 +389,9 @@ /* member role */ "admin" = "amministratore"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Gli amministratori possono bloccare un membro per tutti."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Gli amministratori possono creare i link per entrare nei gruppi."; @@ -416,6 +431,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Tutti i tuoi contatti resteranno connessi. L'aggiornamento del profilo verrà inviato ai tuoi contatti."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Tutti i tuoi contatti, le conversazioni e i file verranno criptati in modo sicuro e caricati in blocchi sui relay XFTP configurati."; + /* No comment provided by engineer. */ "Allow" = "Consenti"; @@ -497,6 +515,9 @@ /* No comment provided by engineer. */ "App build: %@" = "Build dell'app: %@"; +/* No comment provided by engineer. */ +"App data migration" = "Migrazione dati dell'app"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "L'app cripta i nuovi file locali (eccetto i video)."; @@ -518,6 +539,15 @@ /* No comment provided by engineer. */ "Appearance" = "Aspetto"; +/* No comment provided by engineer. */ +"Apply" = "Applica"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Archivia e carica"; + +/* No comment provided by engineer. */ +"Archiving database" = "Archiviazione del database"; + /* No comment provided by engineer. */ "Attach" = "Allega"; @@ -602,13 +632,13 @@ /* No comment provided by engineer. */ "Block member?" = "Bloccare il membro?"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "blocked" = "bloccato"; /* rcv group event chat item */ "blocked %@" = "ha bloccato %@"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "bloccato dall'amministratore"; /* No comment provided by engineer. */ @@ -665,6 +695,9 @@ /* No comment provided by engineer. */ "Cancel" = "Annulla"; +/* No comment provided by engineer. */ +"Cancel migration" = "Annulla migrazione"; + /* feature offered item */ "cancelled %@" = "annullato %@"; @@ -744,6 +777,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "La chat è ferma. Se hai già usato questo database su un altro dispositivo, dovresti trasferirlo prima di avviare la chat."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Chat migrata!"; + /* No comment provided by engineer. */ "Chat preferences" = "Preferenze della chat"; @@ -756,6 +792,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Interfaccia cinese e spagnola"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Scegli _Migra da un altro dispositivo_ sul nuovo dispositivo e scansione il codice QR."; + /* No comment provided by engineer. */ "Choose file" = "Scegli file"; @@ -801,6 +840,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Conferma aggiornamenti database"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Conferma le impostazioni di rete"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Conferma nuova password…"; @@ -810,6 +852,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Conferma password"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Conferma che ricordi la password del database da migrare."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Conferma caricamento"; + /* server test step */ "Connect" = "Connetti"; @@ -1008,6 +1056,9 @@ /* No comment provided by engineer. */ "Created on %@" = "Creato il %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "Creazione link dell'archivio"; + /* No comment provided by engineer. */ "Creating link…" = "Creazione link…"; @@ -1155,6 +1206,9 @@ /* No comment provided by engineer. */ "Delete database" = "Elimina database"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Elimina il database da questo dispositivo"; + /* server test step */ "Delete file" = "Elimina file"; @@ -1347,9 +1401,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Esegui downgrade e apri chat"; +/* No comment provided by engineer. */ +"Download failed" = "Scaricamento fallito"; + /* server test step */ "Download file" = "Scarica file"; +/* No comment provided by engineer. */ +"Downloading archive" = "Scaricamento archivio"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Scaricamento dettagli del link"; + /* No comment provided by engineer. */ "Duplicate display name!" = "Nome da mostrare doppio!"; @@ -1383,6 +1446,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Attiva per tutti"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Attivala nelle chat dirette (BETA)!"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Attivare le notifiche istantanee?"; @@ -1497,6 +1563,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Inserisci il codice di accesso"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Inserisci password"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Inserisci la password…"; @@ -1536,6 +1605,9 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Errore di aggiunta membro/i"; +/* No comment provided by engineer. */ +"Error allowing contact PQ encryption" = "Errore nel consentire la crittografia PQ al contatto"; + /* No comment provided by engineer. */ "Error changing address" = "Errore nella modifica dell'indirizzo"; @@ -1590,6 +1662,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Errore nell'eliminazione del profilo utente"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Errore di scaricamento dell'archivio"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "Errore nell'attivazione delle ricevute di consegna!"; @@ -1635,6 +1710,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Errore nel salvataggio della password nel portachiavi"; +/* when migrating */ +"Error saving settings" = "Errore di salvataggio delle impostazioni"; + /* No comment provided by engineer. */ "Error saving user password" = "Errore nel salvataggio della password utente"; @@ -1677,6 +1755,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Errore nell'aggiornamento della privacy dell'utente"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Errore di invio dell'archivio"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Errore di verifica della password:"; + /* No comment provided by engineer. */ "Error: " = "Errore: "; @@ -1710,6 +1794,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Archivio database esportato."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "Il file esportato non esiste"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Esportazione archivio database…"; @@ -1752,6 +1839,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Filtra le chat non lette e preferite."; +/* No comment provided by engineer. */ +"Finalize migration" = "Finalizza la migrazione"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Finalizza la migrazione su un altro dispositivo."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "Finalmente le abbiamo! 🚀"; @@ -1935,6 +2028,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "Come usare i tuoi server"; +/* No comment provided by engineer. */ +"Hungarian interface" = "Interfaccia in ungherese"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "Server ICE (uno per riga)"; @@ -1974,6 +2070,12 @@ /* No comment provided by engineer. */ "Import database" = "Importa database"; +/* No comment provided by engineer. */ +"Import failed" = "Importazione fallita"; + +/* No comment provided by engineer. */ +"Importing archive" = "Importazione archivio"; + /* No comment provided by engineer. */ "Improved message delivery" = "Consegna dei messaggi migliorata"; @@ -1983,6 +2085,9 @@ /* No comment provided by engineer. */ "Improved server configuration" = "Configurazione del server migliorata"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "Per continuare, la chat deve essere fermata."; + /* No comment provided by engineer. */ "In reply to" = "In risposta a"; @@ -2067,6 +2172,9 @@ /* No comment provided by engineer. */ "Invalid link" = "Link non valido"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Conferma di migrazione non valida"; + /* No comment provided by engineer. */ "Invalid name!" = "Nome non valido!"; @@ -2331,6 +2439,9 @@ /* No comment provided by engineer. */ "Message text" = "Testo del messaggio"; +/* No comment provided by engineer. */ +"Message too large" = "Messaggio troppo grande"; + /* No comment provided by engineer. */ "Messages" = "Messaggi"; @@ -2340,9 +2451,36 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "I messaggi da %@ verranno mostrati!"; +/* 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." = "I messaggi, i file e le chiamate sono protetti da **crittografia end-to-end** con perfect forward secrecy, ripudio e recupero da intrusione."; + +/* 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." = "I messaggi, i file e le chiamate sono protetti da **crittografia e2e resistente al quantistico** con perfect forward secrecy, ripudio e recupero da intrusione."; + +/* No comment provided by engineer. */ +"Migrate device" = "Migra dispositivo"; + +/* No comment provided by engineer. */ +"Migrate from another device" = "Migra da un altro dispositivo"; + +/* No comment provided by engineer. */ +"Migrate here" = "Migra qui"; + +/* No comment provided by engineer. */ +"Migrate to another device" = "Migra ad un altro dispositivo"; + +/* No comment provided by engineer. */ +"Migrate to another device via QR code." = "Migra ad un altro dispositivo via codice QR."; + +/* No comment provided by engineer. */ +"Migrating" = "Migrazione"; + /* No comment provided by engineer. */ "Migrating database archive…" = "Migrazione archivio del database…"; +/* No comment provided by engineer. */ +"Migration complete" = "Migrazione completata"; + /* No comment provided by engineer. */ "Migration error:" = "Errore di migrazione:"; @@ -2373,7 +2511,7 @@ /* copied message info */ "Moderated at: %@" = "Moderato il: %@"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "moderato da %@"; /* time unit */ @@ -2600,6 +2738,9 @@ /* No comment provided by engineer. */ "Open group" = "Apri gruppo"; +/* authentication reason */ +"Open migration to another device" = "Apri migrazione ad un altro dispositivo"; + /* No comment provided by engineer. */ "Open Settings" = "Apri le impostazioni"; @@ -2612,9 +2753,15 @@ /* No comment provided by engineer. */ "Opening app…" = "Apertura dell'app…"; +/* No comment provided by engineer. */ +"Or paste archive link" = "O incolla il link dell'archivio"; + /* No comment provided by engineer. */ "Or scan QR code" = "O scansiona il codice QR"; +/* No comment provided by engineer. */ +"Or securely share this file link" = "O condividi in modo sicuro questo link del file"; + /* No comment provided by engineer. */ "Or show this code" = "O mostra questo codice"; @@ -2666,6 +2813,9 @@ /* message decrypt error item */ "Permanent decryption error" = "Errore di decifrazione"; +/* No comment provided by engineer. */ +"Picture-in-picture calls" = "Chiamate picture-in-picture"; + /* No comment provided by engineer. */ "PING count" = "Conteggio PING"; @@ -2684,6 +2834,9 @@ /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Controlla le preferenze tue e del tuo contatto."; +/* No comment provided by engineer. */ +"Please confirm that network settings are correct for this device." = "Conferma che le impostazioni di rete sono corrette per questo dispositivo."; + /* No comment provided by engineer. */ "Please contact developers.\nError: %@" = "Contatta gli sviluppatori.\nErrore: %@"; @@ -2717,6 +2870,9 @@ /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Probabilmente l'impronta del certificato nell'indirizzo del server è sbagliata"; +/* No comment provided by engineer. */ +"Post-quantum E2EE" = "E2EE post-quantistica"; + /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Conserva la bozza dell'ultimo messaggio, con gli allegati."; @@ -2798,6 +2954,15 @@ /* No comment provided by engineer. */ "Push notifications" = "Notifiche push"; +/* No comment provided by engineer. */ +"Push server" = "Server push"; + +/* chat item text */ +"quantum resistant e2e encryption" = "crittografia e2e resistente al quantistico"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "Crittografia resistente al quantistico"; + /* No comment provided by engineer. */ "Rate the app" = "Valuta l'app"; @@ -2933,9 +3098,18 @@ /* No comment provided by engineer. */ "Repeat connection request?" = "Ripetere la richiesta di connessione?"; +/* No comment provided by engineer. */ +"Repeat download" = "Ripeti scaricamento"; + +/* No comment provided by engineer. */ +"Repeat import" = "Ripeti importazione"; + /* No comment provided by engineer. */ "Repeat join request?" = "Ripetere la richiesta di ingresso?"; +/* No comment provided by engineer. */ +"Repeat upload" = "Ripeti caricamento"; + /* chat item action */ "Reply" = "Rispondi"; @@ -2993,6 +3167,9 @@ /* No comment provided by engineer. */ "Run chat" = "Avvia chat"; +/* No comment provided by engineer. */ +"Safer groups" = "Gruppi più sicuri"; + /* chat item action */ "Save" = "Salva"; @@ -3233,6 +3410,9 @@ /* No comment provided by engineer. */ "Set passcode" = "Imposta codice"; +/* No comment provided by engineer. */ +"Set passphrase" = "Imposta password"; + /* No comment provided by engineer. */ "Set passphrase to export" = "Imposta la password per esportare"; @@ -3278,6 +3458,9 @@ /* No comment provided by engineer. */ "Show preview" = "Mostra anteprima"; +/* No comment provided by engineer. */ +"Show QR code" = "Mostra codice QR"; + /* No comment provided by engineer. */ "Show:" = "Mostra:"; @@ -3338,6 +3521,9 @@ /* notification title */ "Somebody" = "Qualcuno"; +/* chat item text */ +"standard end-to-end encryption" = "crittografia end-to-end standard"; + /* No comment provided by engineer. */ "Start chat" = "Avvia chat"; @@ -3353,6 +3539,9 @@ /* No comment provided by engineer. */ "Stop" = "Ferma"; +/* No comment provided by engineer. */ +"Stop chat" = "Ferma la chat"; + /* No comment provided by engineer. */ "Stop chat to enable database actions" = "Ferma la chat per attivare le azioni del database"; @@ -3380,6 +3569,9 @@ /* authentication reason */ "Stop SimpleX" = "Ferma SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "Arresto della chat"; + /* No comment provided by engineer. */ "strike" = "barrato"; @@ -3530,6 +3722,12 @@ /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Questa azione non può essere annullata: il tuo profilo, i contatti, i messaggi e i file andranno persi in modo irreversibile."; +/* E2EE info chat item */ +"This chat is protected by end-to-end encryption." = "Questa chat è protetta da crittografia end-to-end."; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "Questa chat è protetta da crittografia end-to-end resistente al quantistico."; + /* notification title */ "this contact" = "questo contatto"; @@ -3722,9 +3920,15 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Aggiorna e apri chat"; +/* No comment provided by engineer. */ +"Upload failed" = "Invio fallito"; + /* server test step */ "Upload file" = "Invia file"; +/* No comment provided by engineer. */ +"Uploading archive" = "Invio dell'archivio"; + /* No comment provided by engineer. */ "Use .onion hosts" = "Usa gli host .onion"; @@ -3755,6 +3959,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Usare i server di SimpleX Chat?"; +/* No comment provided by engineer. */ +"Use the app while in the call." = "Usa l'app mentre sei in chiamata."; + /* No comment provided by engineer. */ "User profile" = "Profilo utente"; @@ -3782,6 +3989,12 @@ /* No comment provided by engineer. */ "Verify connections" = "Verifica le connessioni"; +/* No comment provided by engineer. */ +"Verify database passphrase" = "Verifica password del database"; + +/* No comment provided by engineer. */ +"Verify passphrase" = "Verifica password"; + /* No comment provided by engineer. */ "Verify security code" = "Verifica codice di sicurezza"; @@ -3801,7 +4014,7 @@ "via relay" = "via relay"; /* No comment provided by engineer. */ -"Via secure quantum resistant protocol." = "Tramite protocollo sicuro resistente alla quantistica."; +"Via secure quantum resistant protocol." = "Tramite protocollo sicuro resistente al quantistico."; /* No comment provided by engineer. */ "Video call" = "Videochiamata"; @@ -3860,6 +4073,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "vuole connettersi con te!"; +/* No comment provided by engineer. */ +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Attenzione: avviare la chat su più dispositivi non è supportato e provocherà problemi di recapito dei messaggi"; + /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Attenzione: potresti perdere alcuni dati!"; @@ -3875,6 +4091,9 @@ /* No comment provided by engineer. */ "Welcome message" = "Messaggio di benvenuto"; +/* No comment provided by engineer. */ +"Welcome message is too long" = "Il messaggio di benvenuto è troppo lungo"; + /* No comment provided by engineer. */ "What's new" = "Novità"; @@ -3911,6 +4130,9 @@ /* No comment provided by engineer. */ "You" = "Tu"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "**Non devi** usare lo stesso database su due dispositivi."; + /* No comment provided by engineer. */ "You accepted connection" = "Hai accettato la connessione"; @@ -3971,6 +4193,9 @@ /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "Puoi attivarle più tardi nelle impostazioni di privacy e sicurezza dell'app."; +/* No comment provided by engineer. */ +"You can give another try." = "Puoi fare un altro tentativo."; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "Puoi nascondere o silenziare un profilo utente - scorrilo verso destra."; @@ -4155,7 +4380,7 @@ "Your profile" = "Il tuo profilo"; /* No comment provided by engineer. */ -"Your profile **%@** will be shared." = "Il tuo profilo **%@** verrà condiviso."; +"Your profile **%@** will be shared." = "Verrà condiviso il tuo profilo **%@**."; /* No comment provided by engineer. */ "Your profile is stored on your device and shared only with your contacts.\nSimpleX servers cannot see your profile." = "Il tuo profilo è memorizzato sul tuo dispositivo e condiviso solo con i tuoi contatti.\nI server di SimpleX non possono vedere il tuo profilo."; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index fb11e89959..49797a6ac9 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -106,6 +106,9 @@ /* No comment provided by engineer. */ "%@ %@" = "%@ %@"; +/* No comment provided by engineer. */ +"%@ and %@" = "%@ と %@"; + /* No comment provided by engineer. */ "%@ and %@ connected" = "%@ と %@ は接続中"; @@ -2061,7 +2064,7 @@ /* copied message info */ "Moderated at: %@" = "モデレーターによって介入済み: %@"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "%@ によってモデレートされた"; /* time unit */ diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index 91b208ec32..bfd97f3f43 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Meest privé**: gebruik geen SimpleX Chat-notificatie server, controleer berichten regelmatig op de achtergrond (afhankelijk van hoe vaak u de app gebruikt)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Let op**: als u dezelfde database op twee apparaten gebruikt, wordt de decodering van berichten van uw verbindingen verbroken, als veiligheidsmaatregel."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Let op**: u kunt het wachtwoord NIET herstellen of wijzigen als u het kwijtraakt."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Waarschuwing**: voor directe push meldingen is een wachtwoord vereist dat is opgeslagen in de Keychain."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Waarschuwing**: het archief wordt verwijderd."; + /* No comment provided by engineer. */ "*bold*" = "\\*vetgedrukt*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ verbonden"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ gedownload"; + /* notification title */ "%@ is connected!" = "%@ is verbonden!"; @@ -148,6 +157,9 @@ /* No comment provided by engineer. */ "%@ servers" = "%@ servers"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ geüpload"; + /* notification title */ "%@ wants to connect!" = "%@ wil verbinding maken!"; @@ -377,6 +389,9 @@ /* member role */ "admin" = "Beheerder"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Beheerders kunnen een lid voor iedereen blokkeren."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Beheerders kunnen de uitnodiging links naar groepen aanmaken."; @@ -416,6 +431,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Al uw contacten blijven verbonden. Profiel update wordt naar uw contacten verzonden."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Al uw contacten, gesprekken en bestanden worden veilig gecodeerd en in delen geüpload naar geconfigureerde XFTP-relays."; + /* No comment provided by engineer. */ "Allow" = "Toestaan"; @@ -497,6 +515,9 @@ /* No comment provided by engineer. */ "App build: %@" = "App build: %@"; +/* No comment provided by engineer. */ +"App data migration" = "Migratie van app-gegevens"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "App versleutelt nieuwe lokale bestanden (behalve video's)."; @@ -518,6 +539,15 @@ /* No comment provided by engineer. */ "Appearance" = "Uiterlijk"; +/* No comment provided by engineer. */ +"Apply" = "Toepassen"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Archiveren en uploaden"; + +/* No comment provided by engineer. */ +"Archiving database" = "Database archiveren"; + /* No comment provided by engineer. */ "Attach" = "Bijvoegen"; @@ -602,13 +632,13 @@ /* No comment provided by engineer. */ "Block member?" = "Lid blokkeren?"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "blocked" = "geblokkeerd"; /* rcv group event chat item */ "blocked %@" = "geblokkeerd %@"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "geblokkeerd door beheerder"; /* No comment provided by engineer. */ @@ -665,6 +695,9 @@ /* No comment provided by engineer. */ "Cancel" = "Annuleren"; +/* No comment provided by engineer. */ +"Cancel migration" = "Migratie annuleren"; + /* feature offered item */ "cancelled %@" = "geannuleerd %@"; @@ -744,6 +777,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Chat is gestopt. Als je deze database al op een ander apparaat hebt gebruikt, moet je deze terugzetten voordat je met chatten begint."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Chat gemigreerd!"; + /* No comment provided by engineer. */ "Chat preferences" = "Gesprek voorkeuren"; @@ -756,6 +792,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Chinese en Spaanse interface"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Kies _Migreren vanaf een ander apparaat_ op het nieuwe apparaat en scan de QR-code."; + /* No comment provided by engineer. */ "Choose file" = "Kies bestand"; @@ -801,6 +840,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Bevestig database upgrades"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Bevestig netwerk instellingen"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Bevestig nieuw wachtwoord…"; @@ -810,6 +852,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Bevestig wachtwoord"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Bevestig dat u het wachtwoord voor de database onthoudt om deze te migreren."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Bevestig het uploaden"; + /* server test step */ "Connect" = "Verbind"; @@ -1008,6 +1056,9 @@ /* No comment provided by engineer. */ "Created on %@" = "Gemaakt op %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "Archief link maken"; + /* No comment provided by engineer. */ "Creating link…" = "Link maken…"; @@ -1155,6 +1206,9 @@ /* No comment provided by engineer. */ "Delete database" = "Database verwijderen"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Verwijder de database van dit apparaat"; + /* server test step */ "Delete file" = "Verwijder bestand"; @@ -1347,9 +1401,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Downgraden en chat openen"; +/* No comment provided by engineer. */ +"Download failed" = "Download mislukt"; + /* server test step */ "Download file" = "Download bestand"; +/* No comment provided by engineer. */ +"Downloading archive" = "Archief downloaden"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Link gegevens downloaden"; + /* No comment provided by engineer. */ "Duplicate display name!" = "Dubbele weergavenaam!"; @@ -1383,6 +1446,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Inschakelen voor iedereen"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Activeer in directe chats (BETA)!"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Onmiddellijke meldingen inschakelen?"; @@ -1497,6 +1563,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Voer toegangscode in"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Voer het wachtwoord in"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Voer wachtwoord in…"; @@ -1536,6 +1605,9 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Fout bij het toevoegen van leden"; +/* No comment provided by engineer. */ +"Error allowing contact PQ encryption" = "Fout bij het toestaan van contact PQ-versleuteling"; + /* No comment provided by engineer. */ "Error changing address" = "Fout bij wijzigen van adres"; @@ -1590,6 +1662,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Fout bij het verwijderen van gebruikers profiel"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Fout bij het downloaden van het archief"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "Fout bij het inschakelen van ontvangst bevestiging!"; @@ -1635,6 +1710,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Fout bij opslaan van wachtwoord in de keychain"; +/* when migrating */ +"Error saving settings" = "Fout bij opslaan van instellingen"; + /* No comment provided by engineer. */ "Error saving user password" = "Fout bij opslaan gebruikers wachtwoord"; @@ -1677,6 +1755,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Fout bij updaten van gebruikers privacy"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Fout bij het uploaden van het archief"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Fout bij het verifiëren van het wachtwoord:"; + /* No comment provided by engineer. */ "Error: " = "Fout: "; @@ -1710,6 +1794,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Geëxporteerd database archief."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "Geëxporteerd bestand bestaat niet"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Database archief exporteren…"; @@ -1752,6 +1839,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Filter ongelezen en favoriete chats."; +/* No comment provided by engineer. */ +"Finalize migration" = "Voltooi de migratie"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Voltooi de migratie op een ander apparaat."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "Eindelijk, we hebben ze! 🚀"; @@ -1935,6 +2028,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "Hoe u uw servers gebruikt"; +/* No comment provided by engineer. */ +"Hungarian interface" = "Hongaarse interface"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "ICE servers (één per lijn)"; @@ -1974,6 +2070,12 @@ /* No comment provided by engineer. */ "Import database" = "Database importeren"; +/* No comment provided by engineer. */ +"Import failed" = "Importeren is mislukt"; + +/* No comment provided by engineer. */ +"Importing archive" = "Archief importeren"; + /* No comment provided by engineer. */ "Improved message delivery" = "Verbeterde berichtbezorging"; @@ -1983,6 +2085,9 @@ /* No comment provided by engineer. */ "Improved server configuration" = "Verbeterde serverconfiguratie"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "Om verder te kunnen gaan, moet de chat worden gestopt."; + /* No comment provided by engineer. */ "In reply to" = "In antwoord op"; @@ -1999,7 +2104,7 @@ "Incognito mode protects your privacy by using a new random profile for each contact." = "Incognito -modus beschermt uw privacy met behulp van een nieuw willekeurig profiel voor elk contact."; /* chat list item description */ -"incognito via contact address link" = "incognito via contactadres link"; +"incognito via contact address link" = "incognito via contact adres link"; /* chat list item description */ "incognito via group link" = "incognito via groep link"; @@ -2067,6 +2172,9 @@ /* No comment provided by engineer. */ "Invalid link" = "Ongeldige link"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Ongeldige migratie bevestiging"; + /* No comment provided by engineer. */ "Invalid name!" = "Ongeldige naam!"; @@ -2206,7 +2314,7 @@ "Leave group?" = "Groep verlaten?"; /* rcv group event chat item */ -"left" = "verlaten"; +"left" = "is vertrokken"; /* email subject */ "Let's talk in SimpleX Chat" = "Laten we praten in SimpleX Chat"; @@ -2331,6 +2439,9 @@ /* No comment provided by engineer. */ "Message text" = "Bericht tekst"; +/* No comment provided by engineer. */ +"Message too large" = "Bericht te groot"; + /* No comment provided by engineer. */ "Messages" = "Berichten"; @@ -2340,9 +2451,36 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Berichten van %@ worden getoond!"; +/* 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." = "Berichten, bestanden en oproepen worden beschermd door **end-to-end codering** met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel."; + +/* 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." = "Berichten, bestanden en oproepen worden beschermd door **kwantumbestendige e2e encryptie** met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel."; + +/* No comment provided by engineer. */ +"Migrate device" = "Apparaat migreren"; + +/* No comment provided by engineer. */ +"Migrate from another device" = "Migreer vanaf een ander apparaat"; + +/* No comment provided by engineer. */ +"Migrate here" = "Migreer hierheen"; + +/* No comment provided by engineer. */ +"Migrate to another device" = "Migreer naar een ander apparaat"; + +/* No comment provided by engineer. */ +"Migrate to another device via QR code." = "Migreer naar een ander apparaat via QR-code."; + +/* No comment provided by engineer. */ +"Migrating" = "Migreren"; + /* No comment provided by engineer. */ "Migrating database archive…" = "Database archief migreren…"; +/* No comment provided by engineer. */ +"Migration complete" = "Migratie voltooid"; + /* No comment provided by engineer. */ "Migration error:" = "Migratiefout:"; @@ -2373,7 +2511,7 @@ /* copied message info */ "Moderated at: %@" = "Gemodereerd op: %@"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "gemodereerd door %@"; /* time unit */ @@ -2600,6 +2738,9 @@ /* No comment provided by engineer. */ "Open group" = "Open groep"; +/* authentication reason */ +"Open migration to another device" = "Open de migratie naar een ander apparaat"; + /* No comment provided by engineer. */ "Open Settings" = "Open instellingen"; @@ -2612,9 +2753,15 @@ /* No comment provided by engineer. */ "Opening app…" = "App openen…"; +/* No comment provided by engineer. */ +"Or paste archive link" = "Of plak de archief link"; + /* No comment provided by engineer. */ "Or scan QR code" = "Of scan de QR-code"; +/* No comment provided by engineer. */ +"Or securely share this file link" = "Of deel deze bestands link veilig"; + /* No comment provided by engineer. */ "Or show this code" = "Of laat deze code zien"; @@ -2666,6 +2813,9 @@ /* message decrypt error item */ "Permanent decryption error" = "Decodering fout"; +/* No comment provided by engineer. */ +"Picture-in-picture calls" = "Beeld-in-beeld oproepen"; + /* No comment provided by engineer. */ "PING count" = "PING count"; @@ -2684,6 +2834,9 @@ /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Controleer de uwe en uw contact voorkeuren."; +/* No comment provided by engineer. */ +"Please confirm that network settings are correct for this device." = "Controleer of de netwerk instellingen correct zijn voor dit apparaat."; + /* No comment provided by engineer. */ "Please contact developers.\nError: %@" = "Neem contact op met ontwikkelaars.\nFout: %@"; @@ -2717,6 +2870,9 @@ /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Mogelijk is de certificaat vingerafdruk in het server adres onjuist"; +/* No comment provided by engineer. */ +"Post-quantum E2EE" = "Post-quantum E2EE"; + /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Bewaar het laatste berichtconcept, met bijlagen."; @@ -2798,6 +2954,15 @@ /* No comment provided by engineer. */ "Push notifications" = "Push meldingen"; +/* No comment provided by engineer. */ +"Push server" = "Push server"; + +/* chat item text */ +"quantum resistant e2e encryption" = "quantum bestendige e2e-codering"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "quantum bestendige encryptie"; + /* No comment provided by engineer. */ "Rate the app" = "Beoordeel de app"; @@ -2933,9 +3098,18 @@ /* No comment provided by engineer. */ "Repeat connection request?" = "Verbindingsverzoek herhalen?"; +/* No comment provided by engineer. */ +"Repeat download" = "Herhaal het downloaden"; + +/* No comment provided by engineer. */ +"Repeat import" = "Herhaal import"; + /* No comment provided by engineer. */ "Repeat join request?" = "Deelnameverzoek herhalen?"; +/* No comment provided by engineer. */ +"Repeat upload" = "Herhaal het uploaden"; + /* chat item action */ "Reply" = "Antwoord"; @@ -2993,6 +3167,9 @@ /* No comment provided by engineer. */ "Run chat" = "Chat uitvoeren"; +/* No comment provided by engineer. */ +"Safer groups" = "Veiligere groepen"; + /* chat item action */ "Save" = "Opslaan"; @@ -3069,7 +3246,7 @@ "Search bar accepts invitation links." = "Zoekbalk accepteert uitnodigingslinks."; /* No comment provided by engineer. */ -"Search or paste SimpleX link" = "Zoek of plak een SimpleX link"; +"Search or paste SimpleX link" = "Zoeken of plak een SimpleX link"; /* network option */ "sec" = "sec"; @@ -3233,6 +3410,9 @@ /* No comment provided by engineer. */ "Set passcode" = "Toegangscode instellen"; +/* No comment provided by engineer. */ +"Set passphrase" = "Wachtwoord instellen"; + /* No comment provided by engineer. */ "Set passphrase to export" = "Wachtwoord instellen om te exporteren"; @@ -3278,6 +3458,9 @@ /* No comment provided by engineer. */ "Show preview" = "Toon voorbeeld"; +/* No comment provided by engineer. */ +"Show QR code" = "Toon QR-code"; + /* No comment provided by engineer. */ "Show:" = "Toon:"; @@ -3338,6 +3521,9 @@ /* notification title */ "Somebody" = "Iemand"; +/* chat item text */ +"standard end-to-end encryption" = "standaard end-to-end encryptie"; + /* No comment provided by engineer. */ "Start chat" = "Begin gesprek"; @@ -3353,6 +3539,9 @@ /* No comment provided by engineer. */ "Stop" = "Stop"; +/* No comment provided by engineer. */ +"Stop chat" = "Stop chat"; + /* No comment provided by engineer. */ "Stop chat to enable database actions" = "Stop de chat om database acties mogelijk te maken"; @@ -3380,6 +3569,9 @@ /* authentication reason */ "Stop SimpleX" = "Stop SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "Chat stoppen"; + /* No comment provided by engineer. */ "strike" = "staking"; @@ -3530,6 +3722,12 @@ /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Deze actie kan niet ongedaan worden gemaakt. Uw profiel, contacten, berichten en bestanden gaan onomkeerbaar verloren."; +/* E2EE info chat item */ +"This chat is protected by end-to-end encryption." = "Deze chat is beveiligd met end-to-end codering."; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "Deze chat wordt beschermd door quantum bestendige end-to-end codering."; + /* notification title */ "this contact" = "dit contact"; @@ -3722,9 +3920,15 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Upgrade en open chat"; +/* No comment provided by engineer. */ +"Upload failed" = "Upload mislukt"; + /* server test step */ "Upload file" = "Upload bestand"; +/* No comment provided by engineer. */ +"Uploading archive" = "Archief uploaden"; + /* No comment provided by engineer. */ "Use .onion hosts" = "Gebruik .onion-hosts"; @@ -3755,6 +3959,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "SimpleX Chat servers gebruiken?"; +/* No comment provided by engineer. */ +"Use the app while in the call." = "Gebruik de app tijdens het gesprek."; + /* No comment provided by engineer. */ "User profile" = "Gebruikers profiel"; @@ -3782,6 +3989,12 @@ /* No comment provided by engineer. */ "Verify connections" = "Controleer verbindingen"; +/* No comment provided by engineer. */ +"Verify database passphrase" = "Controleer het wachtwoord van de database"; + +/* No comment provided by engineer. */ +"Verify passphrase" = "Controleer het wachtwoord"; + /* No comment provided by engineer. */ "Verify security code" = "Controleer de beveiligingscode"; @@ -3789,7 +4002,7 @@ "Via browser" = "Via browser"; /* chat list item description */ -"via contact address link" = "via contactadres link"; +"via contact address link" = "via contact adres link"; /* chat list item description */ "via group link" = "via groep link"; @@ -3860,6 +4073,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "wil met je in contact komen!"; +/* No comment provided by engineer. */ +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Waarschuwing: het starten van de chat op meerdere apparaten wordt niet ondersteund en zal leiden tot mislukte bezorging van berichten"; + /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Waarschuwing: u kunt sommige gegevens verliezen!"; @@ -3875,6 +4091,9 @@ /* No comment provided by engineer. */ "Welcome message" = "Welkomst bericht"; +/* No comment provided by engineer. */ +"Welcome message is too long" = "Welkomstbericht is te lang"; + /* No comment provided by engineer. */ "What's new" = "Wat is er nieuw"; @@ -3911,6 +4130,9 @@ /* No comment provided by engineer. */ "You" = "Jij"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "U **mag** niet dezelfde database op twee apparaten gebruiken."; + /* No comment provided by engineer. */ "You accepted connection" = "Je hebt de verbinding geaccepteerd"; @@ -3971,6 +4193,9 @@ /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "U kunt ze later inschakelen via de privacy- en beveiligingsinstellingen van de app."; +/* No comment provided by engineer. */ +"You can give another try." = "Je kunt het nog een keer proberen."; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "U kunt een gebruikers profiel verbergen of dempen - veeg het naar rechts."; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index bb07d7c01a..5283ba5ad0 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Najbardziej prywatny**: nie korzystaj z serwera powiadomień SimpleX Chat, sprawdzaj wiadomości okresowo w tle (zależy jak często korzystasz z aplikacji)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "*Uwaga*: używanie tej samej bazy danych na dwóch urządzeniach zepsuje odszyfrowywanie wiadomości twoich połączeń, jako zabezpieczenie."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Uwaga**: NIE będziesz w stanie odzyskać lub zmienić hasła, jeśli je stracisz."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Uwaga**: Natychmiastowe powiadomienia push wymagają hasła zapisanego w Keychain."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Ostrzeżenie**: archiwum zostanie usunięte."; + /* No comment provided by engineer. */ "*bold*" = "\\*pogrubiony*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ połączony"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ pobrane"; + /* notification title */ "%@ is connected!" = "%@ jest połączony!"; @@ -148,6 +157,9 @@ /* No comment provided by engineer. */ "%@ servers" = "%@ serwery"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ wgrane"; + /* notification title */ "%@ wants to connect!" = "%@ chce się połączyć!"; @@ -377,6 +389,9 @@ /* member role */ "admin" = "administrator"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Administratorzy mogą blokować członka dla wszystkich."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Administratorzy mogą tworzyć linki do dołączania do grup."; @@ -416,6 +431,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Wszystkie Twoje kontakty pozostaną połączone. Aktualizacja profilu zostanie wysłana do Twoich kontaktów."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Wszystkie twoje kontakty, konwersacje i pliki będą bezpiecznie szyfrowane i wgrywane w kawałkach do skonfigurowanych przekaźników XFTP."; + /* No comment provided by engineer. */ "Allow" = "Pozwól"; @@ -497,6 +515,9 @@ /* No comment provided by engineer. */ "App build: %@" = "Kompilacja aplikacji: %@"; +/* No comment provided by engineer. */ +"App data migration" = "Migracja danych aplikacji"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Aplikacja szyfruje nowe lokalne pliki (bez filmów)."; @@ -518,6 +539,15 @@ /* No comment provided by engineer. */ "Appearance" = "Wygląd"; +/* No comment provided by engineer. */ +"Apply" = "Zastosuj"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Archiwizuj i prześlij"; + +/* No comment provided by engineer. */ +"Archiving database" = "Archiwizowanie bazy danych"; + /* No comment provided by engineer. */ "Attach" = "Dołącz"; @@ -602,13 +632,13 @@ /* No comment provided by engineer. */ "Block member?" = "Zablokować członka?"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "blocked" = "zablokowany"; /* rcv group event chat item */ "blocked %@" = "zablokowany %@"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "zablokowany przez admina"; /* No comment provided by engineer. */ @@ -665,6 +695,9 @@ /* No comment provided by engineer. */ "Cancel" = "Anuluj"; +/* No comment provided by engineer. */ +"Cancel migration" = "Anuluj migrację"; + /* feature offered item */ "cancelled %@" = "anulowany %@"; @@ -744,6 +777,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Czat został zatrzymany. Jeśli korzystałeś już z tej bazy danych na innym urządzeniu, powinieneś przenieść ją z powrotem przed rozpoczęciem czatu."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Czat zmigrowany!"; + /* No comment provided by engineer. */ "Chat preferences" = "Preferencje czatu"; @@ -756,6 +792,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Chiński i hiszpański interfejs"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Wybierz _Zmigruj z innego urządzenia_ na nowym urządzeniu i zeskanuj kod QR."; + /* No comment provided by engineer. */ "Choose file" = "Wybierz plik"; @@ -801,6 +840,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Potwierdź aktualizacje bazy danych"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Potwierdź ustawienia sieciowe"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Potwierdź nowe hasło…"; @@ -810,6 +852,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Potwierdź hasło"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Potwierdź, że pamiętasz hasło do bazy danych, aby ją zmigrować."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Potwierdź wgranie"; + /* server test step */ "Connect" = "Połącz"; @@ -1008,6 +1056,9 @@ /* No comment provided by engineer. */ "Created on %@" = "Utworzony w dniu %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "Tworzenie linku archiwum"; + /* No comment provided by engineer. */ "Creating link…" = "Tworzenie linku…"; @@ -1155,6 +1206,9 @@ /* No comment provided by engineer. */ "Delete database" = "Usuń bazę danych"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Usuń bazę danych z tego urządzenia"; + /* server test step */ "Delete file" = "Usuń plik"; @@ -1347,9 +1401,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Obniż wersję i otwórz czat"; +/* No comment provided by engineer. */ +"Download failed" = "Pobieranie nie udane"; + /* server test step */ "Download file" = "Pobierz plik"; +/* No comment provided by engineer. */ +"Downloading archive" = "Pobieranie archiwum"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Pobieranie szczegółów linku"; + /* No comment provided by engineer. */ "Duplicate display name!" = "Zduplikowana wyświetlana nazwa!"; @@ -1383,6 +1446,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Włącz dla wszystkich"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Włącz w czatach bezpośrednich (BETA)!"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Włączyć natychmiastowe powiadomienia?"; @@ -1497,6 +1563,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Wprowadź Pin"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Wprowadź hasło"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Wprowadź hasło…"; @@ -1536,6 +1605,9 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Błąd dodawania członka(ów)"; +/* No comment provided by engineer. */ +"Error allowing contact PQ encryption" = "Błąd pozwalania kontaktowi na szyfrowanie PQ"; + /* No comment provided by engineer. */ "Error changing address" = "Błąd zmiany adresu"; @@ -1590,6 +1662,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Błąd usuwania profilu użytkownika"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Błąd pobierania archiwum"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "Błąd włączania potwierdzeń dostawy!"; @@ -1635,6 +1710,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Błąd zapisu hasła do pęku kluczy"; +/* when migrating */ +"Error saving settings" = "Błąd zapisywania ustawień"; + /* No comment provided by engineer. */ "Error saving user password" = "Błąd zapisu hasła użytkownika"; @@ -1677,6 +1755,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Błąd aktualizacji prywatności użytkownika"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Błąd wgrywania archiwum"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Błąd weryfikowania hasła:"; + /* No comment provided by engineer. */ "Error: " = "Błąd: "; @@ -1710,6 +1794,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Wyeksportowane archiwum bazy danych."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "Wyeksportowany plik nie istnieje"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Eksportowanie archiwum bazy danych…"; @@ -1752,6 +1839,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Filtruj nieprzeczytane i ulubione czaty."; +/* No comment provided by engineer. */ +"Finalize migration" = "Dokończ migrację"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Dokończ migrację na innym urządzeniu."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "W końcu je mamy! 🚀"; @@ -1935,6 +2028,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "Jak korzystać z Twoich serwerów"; +/* No comment provided by engineer. */ +"Hungarian interface" = "Węgierski interfejs"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "Serwery ICE (po jednym na linię)"; @@ -1974,6 +2070,12 @@ /* No comment provided by engineer. */ "Import database" = "Importuj bazę danych"; +/* No comment provided by engineer. */ +"Import failed" = "Import nie udał się"; + +/* No comment provided by engineer. */ +"Importing archive" = "Importowanie archiwum"; + /* No comment provided by engineer. */ "Improved message delivery" = "Ulepszona dostawa wiadomości"; @@ -1983,6 +2085,9 @@ /* No comment provided by engineer. */ "Improved server configuration" = "Ulepszona konfiguracja serwera"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "Aby konturować, czat musi zostać zatrzymany."; + /* No comment provided by engineer. */ "In reply to" = "W odpowiedzi na"; @@ -2067,6 +2172,9 @@ /* No comment provided by engineer. */ "Invalid link" = "Nieprawidłowy link"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Nieprawidłowe potwierdzenie migracji"; + /* No comment provided by engineer. */ "Invalid name!" = "Nieprawidłowa nazwa!"; @@ -2331,6 +2439,9 @@ /* No comment provided by engineer. */ "Message text" = "Tekst wiadomości"; +/* No comment provided by engineer. */ +"Message too large" = "Wiadomość jest zbyt duża"; + /* No comment provided by engineer. */ "Messages" = "Wiadomości"; @@ -2340,9 +2451,36 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Wiadomości od %@ zostaną pokazane!"; +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Wiadomości, pliki i połączenia są chronione przez **szyfrowanie end-to-end** z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu."; + +/* 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." = "Wiadomości, pliki i połączenia są chronione przez **kwantowo odporne szyfrowanie end-to-end** z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu."; + +/* No comment provided by engineer. */ +"Migrate device" = "Zmigruj urządzenie"; + +/* No comment provided by engineer. */ +"Migrate from another device" = "Zmigruj z innego urządzenia"; + +/* No comment provided by engineer. */ +"Migrate here" = "Zmigruj tutaj"; + +/* No comment provided by engineer. */ +"Migrate to another device" = "Zmigruj do innego urządzenia"; + +/* No comment provided by engineer. */ +"Migrate to another device via QR code." = "Zmigruj do innego urządzenia przez kod QR."; + +/* No comment provided by engineer. */ +"Migrating" = "Migrowanie"; + /* No comment provided by engineer. */ "Migrating database archive…" = "Migrowanie archiwum bazy danych…"; +/* No comment provided by engineer. */ +"Migration complete" = "Migracja zakończona"; + /* No comment provided by engineer. */ "Migration error:" = "Błąd migracji:"; @@ -2373,7 +2511,7 @@ /* copied message info */ "Moderated at: %@" = "Moderowany o: %@"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "moderowany przez %@"; /* time unit */ @@ -2600,6 +2738,9 @@ /* No comment provided by engineer. */ "Open group" = "Grupa otwarta"; +/* authentication reason */ +"Open migration to another device" = "Otwórz migrację na innym urządzeniu"; + /* No comment provided by engineer. */ "Open Settings" = "Otwórz Ustawienia"; @@ -2612,9 +2753,15 @@ /* No comment provided by engineer. */ "Opening app…" = "Otwieranie aplikacji…"; +/* No comment provided by engineer. */ +"Or paste archive link" = "Lub wklej link archiwum"; + /* No comment provided by engineer. */ "Or scan QR code" = "Lub zeskanuj kod QR"; +/* No comment provided by engineer. */ +"Or securely share this file link" = "Lub bezpiecznie udostępnij ten link pliku"; + /* No comment provided by engineer. */ "Or show this code" = "Lub pokaż ten kod"; @@ -2666,6 +2813,9 @@ /* message decrypt error item */ "Permanent decryption error" = "Stały błąd odszyfrowania"; +/* No comment provided by engineer. */ +"Picture-in-picture calls" = "Połączenia obraz-w-obrazie"; + /* No comment provided by engineer. */ "PING count" = "Liczba PINGÓW"; @@ -2684,6 +2834,9 @@ /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Proszę sprawdzić preferencje Twoje i Twojego kontaktu."; +/* No comment provided by engineer. */ +"Please confirm that network settings are correct for this device." = "Proszę potwierdzić, że ustawienia sieciowe są prawidłowe dla tego urządzenia."; + /* No comment provided by engineer. */ "Please contact developers.\nError: %@" = "Proszę skontaktować się z deweloperami.\nBłąd: %@"; @@ -2717,6 +2870,9 @@ /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Możliwe, że odcisk palca certyfikatu w adresie serwera jest nieprawidłowy"; +/* No comment provided by engineer. */ +"Post-quantum E2EE" = "Postkwantowe E2EE"; + /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Zachowaj ostatnią wersję roboczą wiadomości wraz z załącznikami."; @@ -2798,6 +2954,15 @@ /* No comment provided by engineer. */ "Push notifications" = "Powiadomienia push"; +/* No comment provided by engineer. */ +"Push server" = "Serwer Push"; + +/* chat item text */ +"quantum resistant e2e encryption" = "kwantowo odporne szyfrowanie e2e"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "Kwantowo odporne szyfrowanie"; + /* No comment provided by engineer. */ "Rate the app" = "Oceń aplikację"; @@ -2933,9 +3098,18 @@ /* No comment provided by engineer. */ "Repeat connection request?" = "Powtórzyć prośbę połączenia?"; +/* No comment provided by engineer. */ +"Repeat download" = "Powtórz pobieranie"; + +/* No comment provided by engineer. */ +"Repeat import" = "Powtórz importowanie"; + /* No comment provided by engineer. */ "Repeat join request?" = "Powtórzyć prośbę dołączenia?"; +/* No comment provided by engineer. */ +"Repeat upload" = "Powtórz wgrywanie"; + /* chat item action */ "Reply" = "Odpowiedz"; @@ -2993,6 +3167,9 @@ /* No comment provided by engineer. */ "Run chat" = "Uruchom czat"; +/* No comment provided by engineer. */ +"Safer groups" = "Bezpieczniejsze grupy"; + /* chat item action */ "Save" = "Zapisz"; @@ -3233,6 +3410,9 @@ /* No comment provided by engineer. */ "Set passcode" = "Ustaw pin"; +/* No comment provided by engineer. */ +"Set passphrase" = "Ustaw hasło"; + /* No comment provided by engineer. */ "Set passphrase to export" = "Ustaw hasło do eksportu"; @@ -3278,6 +3458,9 @@ /* No comment provided by engineer. */ "Show preview" = "Pokaż podgląd"; +/* No comment provided by engineer. */ +"Show QR code" = "Pokaż kod QR"; + /* No comment provided by engineer. */ "Show:" = "Pokaż:"; @@ -3338,6 +3521,9 @@ /* notification title */ "Somebody" = "Ktoś"; +/* chat item text */ +"standard end-to-end encryption" = "standardowe szyfrowanie end-to-end"; + /* No comment provided by engineer. */ "Start chat" = "Rozpocznij czat"; @@ -3353,6 +3539,9 @@ /* No comment provided by engineer. */ "Stop" = "Zatrzymaj"; +/* No comment provided by engineer. */ +"Stop chat" = "Zatrzymaj czat"; + /* No comment provided by engineer. */ "Stop chat to enable database actions" = "Zatrzymaj czat, aby umożliwić działania na bazie danych"; @@ -3380,6 +3569,9 @@ /* authentication reason */ "Stop SimpleX" = "Zatrzymaj SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "Zatrzymywanie czatu"; + /* No comment provided by engineer. */ "strike" = "strajk"; @@ -3530,6 +3722,12 @@ /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Tego działania nie można cofnąć - Twój profil, kontakty, wiadomości i pliki zostaną nieodwracalnie utracone."; +/* E2EE info chat item */ +"This chat is protected by end-to-end encryption." = "Ten czat jest chroniony przez szyfrowanie end-to-end."; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "Ten czat jest chroniony przez kwantowo odporne szyfrowanie end-to-end."; + /* notification title */ "this contact" = "ten kontakt"; @@ -3722,9 +3920,15 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Zaktualizuj i otwórz czat"; +/* No comment provided by engineer. */ +"Upload failed" = "Wgrywanie nie udane"; + /* server test step */ "Upload file" = "Prześlij plik"; +/* No comment provided by engineer. */ +"Uploading archive" = "Wgrywanie archiwum"; + /* No comment provided by engineer. */ "Use .onion hosts" = "Użyj hostów .onion"; @@ -3755,6 +3959,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Użyć serwerów SimpleX Chat?"; +/* No comment provided by engineer. */ +"Use the app while in the call." = "Używaj aplikacji podczas połączenia."; + /* No comment provided by engineer. */ "User profile" = "Profil użytkownika"; @@ -3782,6 +3989,12 @@ /* No comment provided by engineer. */ "Verify connections" = "Zweryfikuj połączenia"; +/* No comment provided by engineer. */ +"Verify database passphrase" = "Zweryfikuj hasło bazy danych"; + +/* No comment provided by engineer. */ +"Verify passphrase" = "Zweryfikuj hasło"; + /* No comment provided by engineer. */ "Verify security code" = "Weryfikuj kod bezpieczeństwa"; @@ -3860,6 +4073,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "chce się z Tobą połączyć!"; +/* No comment provided by engineer. */ +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Ostrzeżenie: rozpoczęcie czatu na wielu urządzeniach nie jest wspierane i spowoduje niepowodzenia dostarczania wiadomości"; + /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Uwaga: możesz stracić niektóre dane!"; @@ -3875,6 +4091,9 @@ /* No comment provided by engineer. */ "Welcome message" = "Wiadomość powitalna"; +/* No comment provided by engineer. */ +"Welcome message is too long" = "Wiadomość powitalna jest zbyt długa"; + /* No comment provided by engineer. */ "What's new" = "Co nowego"; @@ -3911,6 +4130,9 @@ /* No comment provided by engineer. */ "You" = "Ty"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "**Nie możesz** używać tej samej bazy na dwóch urządzeniach."; + /* No comment provided by engineer. */ "You accepted connection" = "Zaakceptowałeś połączenie"; @@ -3971,6 +4193,9 @@ /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "Możesz je włączyć później w ustawieniach Prywatności i Bezpieczeństwa aplikacji."; +/* No comment provided by engineer. */ +"You can give another try." = "Możesz spróbować ponownie."; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "Możesz ukryć lub wyciszyć profil użytkownika - przesuń palcem w prawo."; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 871407ee3f..c3f981d144 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Самый конфиденциальный**: не использовать сервер уведомлений SimpleX Chat, проверять сообщения периодически в фоновом режиме (зависит от того насколько часто Вы используете приложение)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Обратите внимание**: использование одной и той же базы данных на двух устройствах нарушит расшифровку сообщений от ваших контактов, как свойство защиты соединений."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Внимание**: Вы не сможете восстановить или поменять пароль, если Вы его потеряете."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Внимание**: для работы мгновенных уведомлений пароль должен быть сохранен в Keychain."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Внимание**: архив будет удален."; + /* No comment provided by engineer. */ "*bold*" = "\\*жирный*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ соединен(а)"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ загружено"; + /* notification title */ "%@ is connected!" = "Установлено соединение с %@!"; @@ -148,6 +157,9 @@ /* No comment provided by engineer. */ "%@ servers" = "%@ серверы"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ загружено"; + /* notification title */ "%@ wants to connect!" = "%@ хочет соединиться!"; @@ -377,6 +389,9 @@ /* member role */ "admin" = "админ"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Админы могут заблокировать члена группы."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Админы могут создать ссылки для вступления в группу."; @@ -416,6 +431,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Все Ваши контакты сохранятся. Обновленный профиль будет отправлен Вашим контактам."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Все ваши контакты, разговоры и файлы будут надежно зашифрованы и загружены на выбранные XFTP серверы."; + /* No comment provided by engineer. */ "Allow" = "Разрешить"; @@ -497,6 +515,9 @@ /* No comment provided by engineer. */ "App build: %@" = "Сборка приложения: %@"; +/* No comment provided by engineer. */ +"App data migration" = "Миграция данных"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Приложение шифрует новые локальные файлы (кроме видео)."; @@ -518,6 +539,15 @@ /* No comment provided by engineer. */ "Appearance" = "Интерфейс"; +/* No comment provided by engineer. */ +"Apply" = "Применить"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Архивировать и загрузить"; + +/* No comment provided by engineer. */ +"Archiving database" = "Подготовка архива"; + /* No comment provided by engineer. */ "Attach" = "Прикрепить"; @@ -602,13 +632,13 @@ /* No comment provided by engineer. */ "Block member?" = "Заблокировать члена группы?"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "blocked" = "заблокировано"; /* rcv group event chat item */ "blocked %@" = "%@ заблокирован"; -/* blocked chat item */ +/* marked deleted chat item preview text */ "blocked by admin" = "заблокировано администратором"; /* No comment provided by engineer. */ @@ -665,6 +695,9 @@ /* No comment provided by engineer. */ "Cancel" = "Отменить"; +/* No comment provided by engineer. */ +"Cancel migration" = "Отменить миграцию"; + /* feature offered item */ "cancelled %@" = "отменил(a) %@"; @@ -744,6 +777,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Чат остановлен. Если вы уже использовали эту базу данных на другом устройстве, перенесите ее обратно до запуска чата."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Чат мигрирован!"; + /* No comment provided by engineer. */ "Chat preferences" = "Предпочтения"; @@ -756,6 +792,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Китайский и Испанский интерфейс"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Выберите _Мигрировать с другого устройства_ на новом устройстве и сосканируйте QR код."; + /* No comment provided by engineer. */ "Choose file" = "Выбрать файл"; @@ -801,6 +840,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Подтвердить обновление базы данных"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Подтвердите настройки сети"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Подтвердите новый пароль…"; @@ -810,6 +852,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Подтвердить пароль"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Подтвердите, что Вы помните пароль базы данных для ее миграции."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Подтвердить загрузку"; + /* server test step */ "Connect" = "Соединиться"; @@ -1008,6 +1056,9 @@ /* No comment provided by engineer. */ "Created on %@" = "Дата создания %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "Создание ссылки на архив"; + /* No comment provided by engineer. */ "Creating link…" = "Создаётся ссылка…"; @@ -1155,6 +1206,9 @@ /* No comment provided by engineer. */ "Delete database" = "Удалить данные чата"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Удалить базу данных с этого устройства"; + /* server test step */ "Delete file" = "Удалить файл"; @@ -1347,9 +1401,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Откатить версию и открыть чат"; +/* No comment provided by engineer. */ +"Download failed" = "Ошибка загрузки"; + /* server test step */ "Download file" = "Загрузка файла"; +/* No comment provided by engineer. */ +"Downloading archive" = "Загрузка архива"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Загрузка ссылки архива"; + /* No comment provided by engineer. */ "Duplicate display name!" = "Имя профиля уже используется!"; @@ -1383,6 +1446,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Включить для всех"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Включите для контактов (BETA)!"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Включить мгновенные уведомления?"; @@ -1497,6 +1563,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Введите Код"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Введите пароль"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Введите пароль…"; @@ -1536,6 +1605,9 @@ /* No comment provided by engineer. */ "Error adding member(s)" = "Ошибка при добавлении членов группы"; +/* No comment provided by engineer. */ +"Error allowing contact PQ encryption" = "Ошибка разрешения квантово-устойчивого шифрования"; + /* No comment provided by engineer. */ "Error changing address" = "Ошибка при изменении адреса"; @@ -1590,6 +1662,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Ошибка удаления профиля пользователя"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Ошибка загрузки архива"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "Ошибка при включении отчётов о доставке!"; @@ -1635,6 +1710,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Ошибка сохранения пароля в Keychain"; +/* when migrating */ +"Error saving settings" = "Ошибка сохранения настроек"; + /* No comment provided by engineer. */ "Error saving user password" = "Ошибка при сохранении пароля пользователя"; @@ -1677,6 +1755,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Ошибка при обновлении конфиденциальности"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Ошибка загрузки архива"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Ошибка подтверждения пароля:"; + /* No comment provided by engineer. */ "Error: " = "Ошибка: "; @@ -1710,6 +1794,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Архив чата экспортирован."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "Экспортированный файл не существует"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Архив чата экспортируется…"; @@ -1752,6 +1839,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Фильтровать непрочитанные и избранные чаты."; +/* No comment provided by engineer. */ +"Finalize migration" = "Завершить миграцию"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Завершите миграцию на другом устройстве."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "Наконец-то, мы их добавили! 🚀"; @@ -1935,6 +2028,9 @@ /* No comment provided by engineer. */ "How to use your servers" = "Как использовать серверы"; +/* No comment provided by engineer. */ +"Hungarian interface" = "Венгерский интерфейс"; + /* No comment provided by engineer. */ "ICE servers (one per line)" = "ICE серверы (один на строке)"; @@ -1974,6 +2070,12 @@ /* No comment provided by engineer. */ "Import database" = "Импорт архива чата"; +/* No comment provided by engineer. */ +"Import failed" = "Ошибка импорта"; + +/* No comment provided by engineer. */ +"Importing archive" = "Импорт архива"; + /* No comment provided by engineer. */ "Improved message delivery" = "Улучшенная доставка сообщений"; @@ -1983,6 +2085,9 @@ /* No comment provided by engineer. */ "Improved server configuration" = "Улучшенная конфигурация серверов"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "Чтобы продолжить, чат должен быть остановлен."; + /* No comment provided by engineer. */ "In reply to" = "В ответ на"; @@ -2067,6 +2172,9 @@ /* No comment provided by engineer. */ "Invalid link" = "Ошибка ссылки"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Ошибка подтверждения миграции"; + /* No comment provided by engineer. */ "Invalid name!" = "Неверное имя!"; @@ -2331,6 +2439,9 @@ /* No comment provided by engineer. */ "Message text" = "Текст сообщения"; +/* No comment provided by engineer. */ +"Message too large" = "Сообщение слишком большое"; + /* No comment provided by engineer. */ "Messages" = "Сообщения"; @@ -2340,9 +2451,36 @@ /* No comment provided by engineer. */ "Messages from %@ will be shown!" = "Сообщения от %@ будут показаны!"; +/* No comment provided by engineer. */ +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Сообщения, файлы и звонки защищены **end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома."; + +/* 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." = "Сообщения, файлы и звонки защищены **квантово-устойчивым end-to-end шифрованием** с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома."; + +/* No comment provided by engineer. */ +"Migrate device" = "Мигрировать устройство"; + +/* No comment provided by engineer. */ +"Migrate from another device" = "Миграция с другого устройства"; + +/* No comment provided by engineer. */ +"Migrate here" = "Мигрировать сюда"; + +/* No comment provided by engineer. */ +"Migrate to another device" = "Мигрировать на другое устройство"; + +/* No comment provided by engineer. */ +"Migrate to another device via QR code." = "Мигрируйте на другое устройство через QR код."; + +/* No comment provided by engineer. */ +"Migrating" = "Миграция"; + /* No comment provided by engineer. */ "Migrating database archive…" = "Данные чата перемещаются…"; +/* No comment provided by engineer. */ +"Migration complete" = "Миграция завершена"; + /* No comment provided by engineer. */ "Migration error:" = "Ошибка при перемещении данных:"; @@ -2373,7 +2511,7 @@ /* copied message info */ "Moderated at: %@" = "Модерировано: %@"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "удалено %@"; /* time unit */ @@ -2600,6 +2738,9 @@ /* No comment provided by engineer. */ "Open group" = "Открыть группу"; +/* authentication reason */ +"Open migration to another device" = "Открытие миграции на другое устройство"; + /* No comment provided by engineer. */ "Open Settings" = "Открыть Настройки"; @@ -2612,9 +2753,15 @@ /* No comment provided by engineer. */ "Opening app…" = "Приложение отрывается…"; +/* No comment provided by engineer. */ +"Or paste archive link" = "Или вставьте ссылку архива"; + /* No comment provided by engineer. */ "Or scan QR code" = "Или отсканируйте QR код"; +/* No comment provided by engineer. */ +"Or securely share this file link" = "Или передайте эту ссылку"; + /* No comment provided by engineer. */ "Or show this code" = "Или покажите этот код"; @@ -2666,6 +2813,9 @@ /* message decrypt error item */ "Permanent decryption error" = "Ошибка расшифровки"; +/* No comment provided by engineer. */ +"Picture-in-picture calls" = "Звонки с картинкой-в-картинке"; + /* No comment provided by engineer. */ "PING count" = "Количество PING"; @@ -2684,6 +2834,9 @@ /* No comment provided by engineer. */ "Please check yours and your contact preferences." = "Проверьте предпочтения Вашего контакта."; +/* No comment provided by engineer. */ +"Please confirm that network settings are correct for this device." = "Пожалуйста, подтвердите, что настройки сети верны для этого устройства."; + /* No comment provided by engineer. */ "Please contact developers.\nError: %@" = "Пожалуйста, сообщите разработчикам.\nОшибка: %@"; @@ -2717,6 +2870,9 @@ /* server test error */ "Possibly, certificate fingerprint in server address is incorrect" = "Возможно, хэш сертификата в адресе сервера неверный"; +/* No comment provided by engineer. */ +"Post-quantum E2EE" = "Квантово-устойчивое E2EE"; + /* No comment provided by engineer. */ "Preserve the last message draft, with attachments." = "Сохранить последний черновик, вместе с вложениями."; @@ -2798,6 +2954,15 @@ /* No comment provided by engineer. */ "Push notifications" = "Доставка уведомлений"; +/* No comment provided by engineer. */ +"Push server" = "Сервер уведомлений"; + +/* chat item text */ +"quantum resistant e2e encryption" = "квантово-устойчивое e2e шифрование"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "Квантово-устойчивое шифрование"; + /* No comment provided by engineer. */ "Rate the app" = "Оценить приложение"; @@ -2933,9 +3098,18 @@ /* No comment provided by engineer. */ "Repeat connection request?" = "Повторить запрос на соединение?"; +/* No comment provided by engineer. */ +"Repeat download" = "Повторить загрузку"; + +/* No comment provided by engineer. */ +"Repeat import" = "Повторить импорт"; + /* No comment provided by engineer. */ "Repeat join request?" = "Повторить запрос на вступление?"; +/* No comment provided by engineer. */ +"Repeat upload" = "Повторить загрузку"; + /* chat item action */ "Reply" = "Ответить"; @@ -2993,6 +3167,9 @@ /* No comment provided by engineer. */ "Run chat" = "Запустить chat"; +/* No comment provided by engineer. */ +"Safer groups" = "Более безопасные группы"; + /* chat item action */ "Save" = "Сохранить"; @@ -3233,6 +3410,9 @@ /* No comment provided by engineer. */ "Set passcode" = "Установить код доступа"; +/* No comment provided by engineer. */ +"Set passphrase" = "Установить пароль"; + /* No comment provided by engineer. */ "Set passphrase to export" = "Установите пароль"; @@ -3278,6 +3458,9 @@ /* No comment provided by engineer. */ "Show preview" = "Показывать уведомления"; +/* No comment provided by engineer. */ +"Show QR code" = "Показать QR код"; + /* No comment provided by engineer. */ "Show:" = "Показать:"; @@ -3338,6 +3521,9 @@ /* notification title */ "Somebody" = "Контакт"; +/* chat item text */ +"standard end-to-end encryption" = "стандартное end-to-end шифрование"; + /* No comment provided by engineer. */ "Start chat" = "Запустить чат"; @@ -3353,6 +3539,9 @@ /* No comment provided by engineer. */ "Stop" = "Остановить"; +/* No comment provided by engineer. */ +"Stop chat" = "Остановить чат"; + /* No comment provided by engineer. */ "Stop chat to enable database actions" = "Остановите чат, чтобы разблокировать операции с архивом чата"; @@ -3380,6 +3569,9 @@ /* authentication reason */ "Stop SimpleX" = "Остановить SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "Остановка чата"; + /* No comment provided by engineer. */ "strike" = "зачеркнуть"; @@ -3530,6 +3722,12 @@ /* No comment provided by engineer. */ "This action cannot be undone - your profile, contacts, messages and files will be irreversibly lost." = "Это действие нельзя отменить — Ваш профиль, контакты, сообщения и файлы будут безвозвратно утеряны."; +/* E2EE info chat item */ +"This chat is protected by end-to-end encryption." = "Чат защищен end-to-end шифрованием."; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "Чат защищен квантово-устойчивым end-to-end шифрованием."; + /* notification title */ "this contact" = "этот контакт"; @@ -3722,9 +3920,15 @@ /* No comment provided by engineer. */ "Upgrade and open chat" = "Обновить и открыть чат"; +/* No comment provided by engineer. */ +"Upload failed" = "Ошибка загрузки"; + /* server test step */ "Upload file" = "Загрузка файла"; +/* No comment provided by engineer. */ +"Uploading archive" = "Загрузка архива"; + /* No comment provided by engineer. */ "Use .onion hosts" = "Использовать .onion хосты"; @@ -3755,6 +3959,9 @@ /* No comment provided by engineer. */ "Use SimpleX Chat servers?" = "Использовать серверы предосталенные SimpleX Chat?"; +/* No comment provided by engineer. */ +"Use the app while in the call." = "Используйте приложение во время звонка."; + /* No comment provided by engineer. */ "User profile" = "Профиль чата"; @@ -3782,6 +3989,12 @@ /* No comment provided by engineer. */ "Verify connections" = "Проверять соединения"; +/* No comment provided by engineer. */ +"Verify database passphrase" = "Проверка пароля базы данных"; + +/* No comment provided by engineer. */ +"Verify passphrase" = "Проверить пароль"; + /* No comment provided by engineer. */ "Verify security code" = "Подтвердить код безопасности"; @@ -3860,6 +4073,9 @@ /* No comment provided by engineer. */ "wants to connect to you!" = "хочет соединиться с Вами!"; +/* No comment provided by engineer. */ +"Warning: starting chat on multiple devices is not supported and will cause message delivery failures" = "Внимание: запуск чата на нескольких устройствах не поддерживается и приведет к сбоям доставки сообщений."; + /* No comment provided by engineer. */ "Warning: you may lose some data!" = "Предупреждение: Вы можете потерять какие то данные!"; @@ -3875,6 +4091,9 @@ /* No comment provided by engineer. */ "Welcome message" = "Приветственное сообщение"; +/* No comment provided by engineer. */ +"Welcome message is too long" = "Приветственное сообщение слишком длинное"; + /* No comment provided by engineer. */ "What's new" = "Новые функции"; @@ -3911,6 +4130,9 @@ /* No comment provided by engineer. */ "You" = "Вы"; +/* No comment provided by engineer. */ +"You **must not** use the same database on two devices." = "Вы **не должны** использовать одну и ту же базу данных на двух устройствах."; + /* No comment provided by engineer. */ "You accepted connection" = "Вы приняли приглашение соединиться"; @@ -3971,6 +4193,9 @@ /* No comment provided by engineer. */ "You can enable them later via app Privacy & Security settings." = "Вы можете включить их позже в настройках Конфиденциальности."; +/* No comment provided by engineer. */ +"You can give another try." = "Вы можете попробовать еще раз."; + /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "Вы можете скрыть профиль или выключить уведомления - потяните его вправо."; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 686c1a58a0..b20c2b4b60 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -1989,7 +1989,7 @@ /* copied message info */ "Moderated at: %@" = "กลั่นกรองที่: %@"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "กลั่นกรองโดย %@"; /* time unit */ diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 75046c08f4..bded9498b9 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -85,6 +85,9 @@ /* No comment provided by engineer. */ "**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**En gizli**: SimpleX Chat bildirim sunucusunu kullanma, arkaplanda mesajları periyodik olarak kontrol edin (uygulamayı ne sıklıkta kullandığınıza bağlıdır)."; +/* No comment provided by engineer. */ +"**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Lütfen dikkat**: Aynı veritabanını iki cihazda kullanmak, güvenlik koruması olarak bağlantılarınızdaki mesajların şifresinin çözülmesini engelleyecektir."; + /* No comment provided by engineer. */ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**Lütfen aklınızda bulunsun**: eğer parolanızı kaybederseniz parolanızı değiştirme veya geri kurtarma ihtimaliniz YOKTUR."; @@ -94,6 +97,9 @@ /* No comment provided by engineer. */ "**Warning**: Instant push notifications require passphrase saved in Keychain." = "**Dikkat**: Anında iletilen bildirimlere Anahtar Zinciri'nde kaydedilmiş parola gereklidir."; +/* No comment provided by engineer. */ +"**Warning**: the archive will be removed." = "**Uyarı**: arşiv silinecektir."; + /* No comment provided by engineer. */ "*bold*" = "\\*kalın*"; @@ -136,6 +142,9 @@ /* No comment provided by engineer. */ "%@ connected" = "%@ bağlandı"; +/* No comment provided by engineer. */ +"%@ downloaded" = "%@ indirildi"; + /* notification title */ "%@ is connected!" = "%@ bağlandı!"; @@ -148,6 +157,9 @@ /* No comment provided by engineer. */ "%@ servers" = "%@ sunucuları"; +/* No comment provided by engineer. */ +"%@ uploaded" = "%@ yüklendi"; + /* notification title */ "%@ wants to connect!" = "%@ bağlanmak istiyor!"; @@ -200,13 +212,16 @@ "%lld members" = "%lld üyeler"; /* No comment provided by engineer. */ -"%lld messages blocked" = "%lld mesajlar engellendi"; +"%lld messages blocked" = "%lld mesaj engellendi"; /* No comment provided by engineer. */ -"%lld messages marked deleted" = "%lld mesajlar silinmiş olarak işaretlendi"; +"%lld messages blocked by admin" = "%lld mesaj yönetici tarafından engellendi"; /* No comment provided by engineer. */ -"%lld messages moderated by %@" = "%lld mesajları %@ tarafından yönetildi"; +"%lld messages marked deleted" = "%lld mesaj silinmiş olarak işaretlendi"; + +/* No comment provided by engineer. */ +"%lld messages moderated by %@" = "%lld mesaj %@ tarafından yönetildi"; /* No comment provided by engineer. */ "%lld minutes" = "%lld dakika"; @@ -245,7 +260,7 @@ "%u messages failed to decrypt." = "%u mesajın şifreleme çözümü başarısız oldu."; /* No comment provided by engineer. */ -"%u messages skipped." = "%u mesaj atlandı"; +"%u messages skipped." = "%u mesajlar atlandı."; /* No comment provided by engineer. */ "`a + b`" = "\\`a + b`"; @@ -254,7 +269,7 @@ "<p>Hi!</p>\n<p><a href=\"%@\">Connect to me via SimpleX Chat</a></p>" = "<p>Merhaba!</p>\n<p><a href=\"%@\">SimpleX Chat ile bana bağlanın</a></p>"; /* No comment provided by engineer. */ -"~strike~" = "\\~strike~"; +"~strike~" = "\\~çizik~"; /* time to disappear */ "0 sec" = "0 saniye"; @@ -374,11 +389,14 @@ /* member role */ "admin" = "yönetici"; +/* No comment provided by engineer. */ +"Admins can block a member for all." = "Yöneticiler bir üyeyi tamamen engelleyebilirler."; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "Yöneticiler gruplara katılmak için bağlantılar oluşturabilir."; /* No comment provided by engineer. */ -"Advanced network settings" = "GGelişmiş ağ ayarları"; +"Advanced network settings" = "Gelişmiş ağ ayarları"; /* chat item text */ "agreeing encryption for %@…" = "%@ için şifreleme kabul ediliyor…"; @@ -398,6 +416,9 @@ /* No comment provided by engineer. */ "All group members will remain connected." = "Tüm grup üyeleri bağlı kalacaktır."; +/* No comment provided by engineer. */ +"All messages will be deleted - this cannot be undone!" = "Tüm mesajlar silinecektir - bu geri alınamaz!"; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Tüm mesajlar silinecektir. Bu, geri alınamaz! Mesajlar, YALNIZCA senin için silinecektir."; @@ -410,6 +431,9 @@ /* No comment provided by engineer. */ "All your contacts will remain connected. Profile update will be sent to your contacts." = "Tüm kişileriniz bağlı kalacaktır. Profil güncellemesi kişilerinize gönderilecektir."; +/* No comment provided by engineer. */ +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Tüm kişileriniz, sohbetleriniz ve dosyalarınız güvenli bir şekilde şifrelenecek ve parçalar halinde yapılandırılmış XFTP rölelerine yüklenecektir."; + /* No comment provided by engineer. */ "Allow" = "İzin ver"; @@ -419,6 +443,9 @@ /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Eğer kişide izin verirse kaybolan mesajlara izin ver."; +/* No comment provided by engineer. */ +"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Konuştuğun kişi, kalıcı olarak silinebilen mesajlara izin veriyorsa sen de ver. (24 saat içinde)"; + /* No comment provided by engineer. */ "Allow message reactions only if your contact allows them." = "Yalnızca kişin mesaj tepkilerine izin veriyorsa sen de ver."; @@ -431,6 +458,9 @@ /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Kendiliğinden yok olan mesajlar göndermeye izin ver."; +/* No comment provided by engineer. */ +"Allow to irreversibly delete sent messages. (24 hours)" = "Gönderilen mesajların kalıcı olarak silinmesine izin ver. (24 saat içinde)"; + /* No comment provided by engineer. */ "Allow to send files and media." = "Dosya ve medya göndermeye izin ver."; @@ -449,6 +479,9 @@ /* No comment provided by engineer. */ "Allow your contacts to call you." = "Kişilerinin seni aramasına izin ver."; +/* No comment provided by engineer. */ +"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "Kişilerinin gönderilen mesajları kalıcı olarak silmesine izin ver. (24 saat içinde)"; + /* No comment provided by engineer. */ "Allow your contacts to send disappearing messages." = "Kişilerinizin kaybolan mesajlar göndermesine izin verin."; @@ -468,7 +501,7 @@ "always" = "her zaman"; /* No comment provided by engineer. */ -"Always use relay" = "Her zaman yönlendirici kulan"; +"Always use relay" = "Her zaman yönlendirici kullan"; /* No comment provided by engineer. */ "An empty chat profile with the provided name is created, and the app opens as usual." = "Verilen adla boş bir sohbet profili oluşturulur ve uygulama her zamanki gibi açılır."; @@ -482,6 +515,9 @@ /* No comment provided by engineer. */ "App build: %@" = "Uygulama sürümü: %@"; +/* No comment provided by engineer. */ +"App data migration" = "Uygulama verisi taşıma"; + /* No comment provided by engineer. */ "App encrypts new local files (except videos)." = "Uygulama yerel dosyaları şifreler (videolar dışında)."; @@ -503,6 +539,15 @@ /* No comment provided by engineer. */ "Appearance" = "Görünüş"; +/* No comment provided by engineer. */ +"Apply" = "Uygula"; + +/* No comment provided by engineer. */ +"Archive and upload" = "Arşivle ve yükle"; + +/* No comment provided by engineer. */ +"Archiving database" = "Veritabanı arşivleniyor"; + /* No comment provided by engineer. */ "Attach" = "Ekle"; @@ -572,6 +617,9 @@ /* No comment provided by engineer. */ "Block" = "Engelle"; +/* No comment provided by engineer. */ +"Block for all" = "Herkes için engelle"; + /* No comment provided by engineer. */ "Block group members" = "Grup üyelerini engelle"; @@ -579,17 +627,32 @@ "Block member" = "Üyeyi engelle"; /* No comment provided by engineer. */ -"Block member?" = "Üyeyi engelle?"; +"Block member for all?" = "Üye herkes için engellensin mi?"; /* No comment provided by engineer. */ +"Block member?" = "Üyeyi engelle?"; + +/* marked deleted chat item preview text */ "blocked" = "engellendi"; +/* rcv group event chat item */ +"blocked %@" = "engellendi %@"; + +/* marked deleted chat item preview text */ +"blocked by admin" = "yönetici tarafından engellendi"; + +/* No comment provided by engineer. */ +"Blocked by admin" = "Yönetici tarafından engellendi"; + /* No comment provided by engineer. */ "bold" = "kalın"; /* No comment provided by engineer. */ "Both you and your contact can add message reactions." = "Sen ve konuştuğun kişi mesaj tepkileri ekleyebilir."; +/* No comment provided by engineer. */ +"Both you and your contact can irreversibly delete sent messages. (24 hours)" = "Konuştuğun kişi ve sen mesajları kalıcı olarak silebilirsiniz. (24 saat içinde)"; + /* No comment provided by engineer. */ "Both you and your contact can make calls." = "Sen ve konuştuğun kişi aramalar yapabilir."; @@ -632,6 +695,9 @@ /* No comment provided by engineer. */ "Cancel" = "İptal et"; +/* No comment provided by engineer. */ +"Cancel migration" = "Taşımayı iptal et"; + /* feature offered item */ "cancelled %@" = "%@ iptal edildi"; @@ -711,6 +777,9 @@ /* No comment provided by engineer. */ "Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Sohbet durduruldu. Bu veritabanını zaten başka bir cihazda kullandıysanız, sohbete başlamadan önce onu geri aktarmalısınız."; +/* No comment provided by engineer. */ +"Chat migrated!" = "Sohbet taşındı!"; + /* No comment provided by engineer. */ "Chat preferences" = "Sohbet tercihleri"; @@ -723,6 +792,9 @@ /* No comment provided by engineer. */ "Chinese and Spanish interface" = "Çince ve İspanyolca arayüz"; +/* No comment provided by engineer. */ +"Choose _Migrate from another device_ on the new device and scan QR code." = "Yeni cihazda _Başka bir cihazdan taşı_ seçeneğini seçin ve QR kodunu tarayın."; + /* No comment provided by engineer. */ "Choose file" = "Dosya seç"; @@ -738,6 +810,9 @@ /* No comment provided by engineer. */ "Clear conversation?" = "Sohbet temizlensin mi?"; +/* No comment provided by engineer. */ +"Clear private notes?" = "Gizli notlar temizlensin mi?"; + /* No comment provided by engineer. */ "Clear verification" = "Doğrulamayı temizle"; @@ -765,6 +840,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Veritabanı geliştirmelerini onayla"; +/* No comment provided by engineer. */ +"Confirm network settings" = "Ağ ayarlarını onaylayın"; + /* No comment provided by engineer. */ "Confirm new passphrase…" = "Yeni parolayı onayla…"; @@ -774,6 +852,12 @@ /* No comment provided by engineer. */ "Confirm password" = "Şifreyi onayla"; +/* No comment provided by engineer. */ +"Confirm that you remember database passphrase to migrate it." = "Taşımak için veritabanı parolasını hatırladığınızı doğrulayın."; + +/* No comment provided by engineer. */ +"Confirm upload" = "Yüklemeyi onayla"; + /* server test step */ "Connect" = "Bağlan"; @@ -876,6 +960,9 @@ /* connection information */ "connection:%@" = "bağlantı:%@"; +/* profile update event chat item */ +"contact %@ changed to %@" = "%1$@ kişisi %2$@ olarak değişti"; + /* No comment provided by engineer. */ "Contact allows" = "Kişi izin veriyor"; @@ -960,9 +1047,18 @@ /* No comment provided by engineer. */ "Create your profile" = "Profilini oluştur"; +/* No comment provided by engineer. */ +"Created at" = "Şurada oluşturuldu"; + +/* copied message info */ +"Created at: %@" = "Şurada oluşturuldu: %@"; + /* No comment provided by engineer. */ "Created on %@" = "%@ de oluşturuldu"; +/* No comment provided by engineer. */ +"Creating archive link" = "Arşiv bağlantısı oluşturuluyor"; + /* No comment provided by engineer. */ "Creating link…" = "Link oluşturuluyor…"; @@ -1066,7 +1162,7 @@ "Delete" = "Sil"; /* No comment provided by engineer. */ -"Delete %lld messages?" = "%lld mesajları silinsin mi?"; +"Delete %lld messages?" = "%lld mesaj silinsin mi?"; /* No comment provided by engineer. */ "Delete address" = "Adresi sil"; @@ -1110,6 +1206,9 @@ /* No comment provided by engineer. */ "Delete database" = "Veritabanını sil"; +/* No comment provided by engineer. */ +"Delete database from this device" = "Veritabanını bu cihazdan sil"; + /* server test step */ "Delete file" = "Dosyayı sil"; @@ -1284,6 +1383,9 @@ /* No comment provided by engineer. */ "Do it later" = "Sonra yap"; +/* No comment provided by engineer. */ +"Do not send history to new members." = "Yeni üyelere geçmişi gönderme."; + /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "Acil aramalar için SimpleX'i KULLANMAYIN."; @@ -1299,9 +1401,18 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Sürüm düşür ve sohbeti aç"; +/* No comment provided by engineer. */ +"Download failed" = "Yükleme başarısız oldu"; + /* server test step */ "Download file" = "Dosya indir"; +/* No comment provided by engineer. */ +"Downloading archive" = "Arşiv indiriliyor"; + +/* No comment provided by engineer. */ +"Downloading link details" = "Bağlantı detayları indiriliyor"; + /* No comment provided by engineer. */ "Duplicate display name!" = "Yinelenen görünen ad!"; @@ -1335,6 +1446,9 @@ /* No comment provided by engineer. */ "Enable for all" = "Herkes için etkinleştir"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "Doğrudan sohbetlerde etkinleştirin (BETA)!"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "Anlık bildirimler etkinleştirilsin mi?"; @@ -1449,6 +1563,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "Şifre gir"; +/* No comment provided by engineer. */ +"Enter passphrase" = "Parolayı girin"; + /* No comment provided by engineer. */ "Enter passphrase…" = "Parola gir…"; @@ -1509,6 +1626,9 @@ /* No comment provided by engineer. */ "Error creating member contact" = "Kişi iletişimi oluşturulurken hata oluştu"; +/* No comment provided by engineer. */ +"Error creating message" = "Mesaj oluşturulurken hata"; + /* No comment provided by engineer. */ "Error creating profile!" = "Profil oluşturulurken hata oluştu!"; @@ -1539,6 +1659,9 @@ /* No comment provided by engineer. */ "Error deleting user profile" = "Kullanıcı profili silinirken hata oluştu"; +/* No comment provided by engineer. */ +"Error downloading the archive" = "Arşiv indirilirken hata oluştu"; + /* No comment provided by engineer. */ "Error enabling delivery receipts!" = "Görüldü bilgisi etkinleştirilirken hata oluştu!"; @@ -1584,6 +1707,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "Parolayı Anahtar Zincirine kaydederken hata oluştu"; +/* when migrating */ +"Error saving settings" = "Ayarlar kaydedilirken hata oluştu"; + /* No comment provided by engineer. */ "Error saving user password" = "Kullanıcı şifresi kaydedilirken hata oluştu"; @@ -1626,6 +1752,12 @@ /* No comment provided by engineer. */ "Error updating user privacy" = "Kullanıcı gizliliği güncellenirken hata oluştu"; +/* No comment provided by engineer. */ +"Error uploading the archive" = "Arşiv yüklenirken hata oluştu"; + +/* No comment provided by engineer. */ +"Error verifying passphrase:" = "Parola doğrulanırken hata oluştu:"; + /* No comment provided by engineer. */ "Error: " = "Hata: "; @@ -1642,7 +1774,7 @@ "Even when disabled in the conversation." = "Konuşma sırasında devre dışı bırakılsa bile."; /* No comment provided by engineer. */ -"event happened" = "etkinliği yaşandı"; +"event happened" = "etkinlik yaşandı"; /* No comment provided by engineer. */ "Exit without saving" = "Kaydetmeden çık"; @@ -1659,6 +1791,9 @@ /* No comment provided by engineer. */ "Exported database archive." = "Dışarı çıkarılmış veritabanı arşivi."; +/* No comment provided by engineer. */ +"Exported file doesn't exist" = "Dışa aktarılan dosya mevcut değil"; + /* No comment provided by engineer. */ "Exporting database archive…" = "Dışarı çıkarılmış veritabanı arşivi…"; @@ -1701,6 +1836,12 @@ /* No comment provided by engineer. */ "Filter unread and favorite chats." = "Favori ve okunmamış sohbetleri filtrele."; +/* No comment provided by engineer. */ +"Finalize migration" = "Taşıma işlemini sonlandır"; + +/* No comment provided by engineer. */ +"Finalize migration on another device." = "Taşıma işlemini başka bir cihazda sonlandırın."; + /* No comment provided by engineer. */ "Finally, we have them! 🚀" = "Sonunda, onlara sahibiz! 🚀"; @@ -1794,6 +1935,9 @@ /* No comment provided by engineer. */ "Group members can add message reactions." = "Grup üyeleri mesaj tepkileri ekleyebilir."; +/* No comment provided by engineer. */ +"Group members can irreversibly delete sent messages. (24 hours)" = "Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde)"; + /* No comment provided by engineer. */ "Group members can send direct messages." = "Grup üyeleri doğrudan mesajlar gönderebilir."; @@ -1860,6 +2004,9 @@ /* No comment provided by engineer. */ "History" = "Geçmiş"; +/* No comment provided by engineer. */ +"History is not sent to new members." = "Yeni üyelere geçmiş gönderilmedi."; + /* time unit */ "hours" = "saat"; @@ -1917,12 +2064,24 @@ /* No comment provided by engineer. */ "Import database" = "Veritabanını içe aktar"; +/* No comment provided by engineer. */ +"Import failed" = "İçe aktarma başarısız oldu"; + +/* No comment provided by engineer. */ +"Importing archive" = "Arşiv içe aktarılıyor"; + +/* No comment provided by engineer. */ +"Improved message delivery" = "İyileştirilmiş mesaj iletimi"; + /* No comment provided by engineer. */ "Improved privacy and security" = "Geliştirilmiş gizlilik ve güvenlik"; /* No comment provided by engineer. */ "Improved server configuration" = "Geliştirilmiş sunucu yapılandırması"; +/* No comment provided by engineer. */ +"In order to continue, chat should be stopped." = "Devam etmek için sohbetin durdurulması gerekiyor."; + /* No comment provided by engineer. */ "In reply to" = "Cevap olarak"; @@ -2001,9 +2160,15 @@ /* invalid chat item */ "invalid data" = "geçersiz veri"; +/* No comment provided by engineer. */ +"Invalid display name!" = "Geçersiz görünen ad!"; + /* No comment provided by engineer. */ "Invalid link" = "Geçersiz bağlantı"; +/* No comment provided by engineer. */ +"Invalid migration confirmation" = "Geçersiz taşıma onayı"; + /* No comment provided by engineer. */ "Invalid name!" = "Geçersiz isim!"; @@ -2038,7 +2203,7 @@ "invited" = "davet edildi"; /* rcv group event chat item */ -"invited %@" = "%@ a davet edildi"; +"invited %@" = "%@ davet edildi"; /* chat list item title */ "invited to connect" = "bağlanmaya davet edildi"; @@ -2091,6 +2256,9 @@ /* No comment provided by engineer. */ "Join group" = "Gruba katıl"; +/* No comment provided by engineer. */ +"Join group conversations" = "Grup sohbetlerine katıl"; + /* No comment provided by engineer. */ "Join group?" = "Gruba katılınsın mı?"; @@ -2226,6 +2394,9 @@ /* No comment provided by engineer. */ "Member" = "Kişi"; +/* profile update event chat item */ +"member %@ changed to %@" = "kişi %1$@ , %2$@ olarak değişti"; + /* rcv group event chat item */ "member connected" = "bağlanıldı"; @@ -2262,6 +2433,9 @@ /* No comment provided by engineer. */ "Message text" = "Mesaj yazısı"; +/* No comment provided by engineer. */ +"Message too large" = "Mesaj çok büyük"; + /* No comment provided by engineer. */ "Messages" = "Mesajlar"; @@ -2304,7 +2478,7 @@ /* copied message info */ "Moderated at: %@" = "%@ de yönetildi"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "%@ tarafından yönetilmekte"; /* time unit */ @@ -2492,6 +2666,9 @@ /* No comment provided by engineer. */ "Only you can add message reactions." = "Sadece siz mesaj tepkileri ekleyebilirsiniz."; +/* No comment provided by engineer. */ +"Only you can irreversibly delete messages (your contact can mark them for deletion). (24 hours)" = "Mesajları yalnızca siz geri döndürülemez şekilde silebilirsiniz (kişiniz bunları silinmek üzere işaretleyebilir). (24 saat içinde)"; + /* No comment provided by engineer. */ "Only you can make calls." = "Sadece sen aramalar yapabilirsin."; @@ -2504,6 +2681,9 @@ /* No comment provided by engineer. */ "Only your contact can add message reactions." = "Sadece karşıdaki kişi mesaj tepkileri ekleyebilir."; +/* No comment provided by engineer. */ +"Only your contact can irreversibly delete messages (you can mark them for deletion). (24 hours)" = "Yalnızca kişiniz mesajları geri alınamaz şekilde silebilir (silinmeleri için işaretleyebilirsiniz). (24 saat içinde)"; + /* No comment provided by engineer. */ "Only your contact can make calls." = "Sadece karşıdaki kişi aramalar yapabilir."; @@ -2564,12 +2744,18 @@ /* No comment provided by engineer. */ "Password to show" = "Gösterilecek şifre"; +/* past/unknown group member */ +"Past member %@" = "Geçmiş üye %@"; + /* No comment provided by engineer. */ "Paste desktop address" = "Bilgisayar adresini yapıştır"; /* No comment provided by engineer. */ "Paste image" = "Fotoğraf yapıştır"; +/* No comment provided by engineer. */ +"Paste link to connect!" = "Bağlanmak için bağlantıyı yapıştır!"; + /* No comment provided by engineer. */ "Paste the link you received" = "Aldığın bağlantıyı yapıştır"; @@ -2657,6 +2843,9 @@ /* No comment provided by engineer. */ "Private filenames" = "Gizli dosya adları"; +/* name of notes to self */ +"Private notes" = "Gizli notlar"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profil ve sunucu bağlantıları"; @@ -2771,6 +2960,9 @@ /* No comment provided by engineer. */ "Receiving via" = "Aracılığıyla alınıyor"; +/* No comment provided by engineer. */ +"Recent history and improved [directory bot](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)." = "Yakın geçmiş ve geliştirilmiş [dizin botu](simplex:/contact#/?v=1-4&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex. im%2FeXSPwqTkKyDO3px4fLf1wx3MvPdjdLW3%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAaiv6MkMH44L2TcYrt_CsX3ZvM11WgbMEUn0hkIKTOho%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion)."; + /* No comment provided by engineer. */ "Recipients see updates as you type them." = "Alıcılar yazdığına göre güncellemeleri görecektir."; @@ -2825,6 +3017,12 @@ /* rcv group event chat item */ "removed %@" = "%@ kaldırıldı"; +/* profile update event chat item */ +"removed contact address" = "kişi adresi silindi"; + +/* profile update event chat item */ +"removed profile picture" = "profil fotoğrafı silindi"; + /* rcv group event chat item */ "removed you" = "sen kaldırıldın"; @@ -2948,6 +3146,9 @@ /* No comment provided by engineer. */ "Save welcome message?" = "Hoşgeldin mesajı kaydedilsin mi?"; +/* message info title */ +"Saved message" = "Kaydedilmiş mesaj"; + /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "Kaydedilmiş WebRTC ICE sunucuları silinecek"; @@ -2969,6 +3170,9 @@ /* No comment provided by engineer. */ "Search" = "Ara"; +/* No comment provided by engineer. */ +"Search bar accepts invitation links." = "Arama çubuğu davet bağlantılarını kabul eder."; + /* No comment provided by engineer. */ "Search or paste SimpleX link" = "Ara veya SimpleX bağlantısını yapıştır"; @@ -3050,6 +3254,9 @@ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "Bunları galeriden veya özel klavyelerden gönder."; +/* No comment provided by engineer. */ +"Send up to 100 last messages to new members." = "Yeni üyelere 100 adete kadar son mesajları gönderin."; + /* No comment provided by engineer. */ "Sender cancelled file transfer." = "Gönderici dosya gönderimini iptal etti."; @@ -3122,6 +3329,12 @@ /* No comment provided by engineer. */ "Set it instead of system authentication." = "Sistem kimlik doğrulaması yerine ayarla."; +/* profile update event chat item */ +"set new contact address" = "yeni kişi adresi ayarla"; + +/* profile update event chat item */ +"set new profile picture" = "yeni profil fotoğrafı ayarla"; + /* No comment provided by engineer. */ "Set passcode" = "Şifre ayarla"; @@ -3273,7 +3486,7 @@ "Stop SimpleX" = "SimpleX'i durdur"; /* No comment provided by engineer. */ -"strike" = "grev"; +"strike" = "çizik"; /* No comment provided by engineer. */ "Submit" = "Gönder"; @@ -3428,6 +3641,9 @@ /* No comment provided by engineer. */ "This device name" = "Bu cihazın ismi"; +/* No comment provided by engineer. */ +"This display name is invalid. Please choose another name." = "Bu görünen ad geçersiz. Lütfen başka bir isim seçin."; + /* No comment provided by engineer. */ "This group has over %lld members, delivery receipts are not sent." = "Bu grubun %lld den fazla üyesi var,görüldü bilgisi gönderilmedi."; @@ -3488,6 +3704,9 @@ /* No comment provided by engineer. */ "Trying to connect to the server used to receive messages from this contact." = "Bu kişiden mesaj almak için kullanılan sunucuya bağlanılmaya çalışılıyor."; +/* No comment provided by engineer. */ +"Turkish interface" = "Türkçe arayüz"; + /* No comment provided by engineer. */ "Turn off" = "Kapat"; @@ -3500,12 +3719,21 @@ /* No comment provided by engineer. */ "Unblock" = "Engeli kaldır"; +/* No comment provided by engineer. */ +"Unblock for all" = "Herkes için engeli kaldır"; + /* No comment provided by engineer. */ "Unblock member" = "Üyenin engelini kaldır"; +/* No comment provided by engineer. */ +"Unblock member for all?" = "Üyenin engeli herkes için kaldırılsın mı?"; + /* No comment provided by engineer. */ "Unblock member?" = "Üyenin engeli kaldırılsın mı?"; +/* rcv group event chat item */ +"unblocked %@" = "engeli kaldırıldı %@"; + /* item status description */ "Unexpected error: %@" = "Beklenmeyen hata: %@"; @@ -3539,6 +3767,9 @@ /* No comment provided by engineer. */ "Unknown error" = "Bilinmeyen hata"; +/* No comment provided by engineer. */ +"unknown status" = "bilinmeyen durum"; + /* No comment provided by engineer. */ "Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "iOS arama arayüzünü kullanmadığınız sürece, kesintileri önlemek için Rahatsız Etmeyin modunu etkinleştirin."; @@ -3563,6 +3794,9 @@ /* No comment provided by engineer. */ "Unread" = "Okunmamış"; +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new members." = "Yeni üyelere 100e kadar en son mesajlar gönderildi."; + /* No comment provided by engineer. */ "Update" = "Güncelle"; @@ -3581,6 +3815,9 @@ /* rcv group event chat item */ "updated group profile" = "grup profili güncellendi"; +/* profile update event chat item */ +"updated profile" = "güncellenmiş profil"; + /* No comment provided by engineer. */ "Updating settings will re-connect the client to all servers." = "Ayarların güncellenmesi, istemciyi tüm sunuculara yeniden bağlayacaktır."; @@ -3689,6 +3926,9 @@ /* No comment provided by engineer. */ "View security code" = "Güvenlik kodunu görüntüle"; +/* chat feature */ +"Visible history" = "Görünür geçmiş"; + /* No comment provided by engineer. */ "Voice message…" = "Sesli mesaj…"; @@ -3752,9 +3992,15 @@ /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Biriyle gizli bir profil paylaştığınızda, bu profil sizi davet ettikleri gruplar için kullanılacaktır."; +/* No comment provided by engineer. */ +"With encrypted files and media." = "Şifrelenmiş dosyalar ve medya ile birlikte."; + /* No comment provided by engineer. */ "With optional welcome message." = "İsteğe bağlı karşılama mesajı ile."; +/* No comment provided by engineer. */ +"With reduced battery usage." = "Azaltılmış pil kullanımı ile birlikte."; + /* No comment provided by engineer. */ "Wrong database passphrase" = "Yanlış veritabanı parolası"; @@ -3815,6 +4061,9 @@ /* No comment provided by engineer. */ "you are observer" = "gözlemcisiniz"; +/* snd group event chat item */ +"you blocked %@" = "engelledin %@"; + /* No comment provided by engineer. */ "You can accept calls from lock screen, without device and app authentication." = "Cihaz ve uygulama kimlik doğrulaması olmadan kilit ekranından çağrı kabul edebilirsiniz."; @@ -3912,7 +4161,7 @@ "You need to allow your contact to send voice messages to be able to send them." = "Sesli mesaj gönderebilmeniz için kişinizin de sesli mesaj göndermesine izin vermeniz gerekir."; /* No comment provided by engineer. */ -"You rejected group invitation" = "Grup davetini reddettiniz."; +"You rejected group invitation" = "Grup davetini reddettiniz"; /* snd group event chat item */ "you removed %@" = "%@'yi çıkarttınız"; @@ -3926,6 +4175,9 @@ /* chat list item description */ "you shared one-time link incognito" = "tek kullanımlık link paylaştınız gizli"; +/* snd group event chat item */ +"you unblocked %@" = "engelini kaldırdın %@"; + /* No comment provided by engineer. */ "You will be connected to group when the group host's device is online, please wait or check later!" = "Grup sahibinin cihazı çevrimiçi olduğunda gruba bağlanacaksınız, lütfen bekleyin veya daha sonra kontrol edin!"; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index be912d785d..37c341f885 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -64,9 +64,15 @@ /* No comment provided by engineer. */ "[Star on GitHub](https://github.com/simplex-chat/simplex-chat)" = "[Зірка на GitHub](https://github.com/simplex-chat/simplex-chat)"; +/* No comment provided by engineer. */ +"**Add contact**: to create a new invitation link, or connect via a link you received." = "**Додати контакт**: створити нове посилання-запрошення або підключитися за отриманим посиланням."; + /* No comment provided by engineer. */ "**Add new contact**: to create your one-time QR Code for your contact." = "**Додати новий контакт**: щоб створити одноразовий QR-код або посилання для свого контакту."; +/* No comment provided by engineer. */ +"**Create group**: to create a new group." = "**Створити групу**: створити нову групу."; + /* No comment provided by engineer. */ "**e2e encrypted** audio call" = "**e2e encrypted** аудіодзвінок"; @@ -196,6 +202,9 @@ /* No comment provided by engineer. */ "%lld messages blocked" = "%lld повідомлень заблоковано"; +/* No comment provided by engineer. */ +"%lld messages blocked by admin" = "%lld повідомлень заблоковано адміністратором"; + /* No comment provided by engineer. */ "%lld messages marked deleted" = "%lld повідомлень позначено як видалені"; @@ -338,6 +347,9 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "Додайте адресу до свого профілю, щоб ваші контакти могли поділитися нею з іншими людьми. Повідомлення про оновлення профілю буде надіслано вашим контактам."; +/* No comment provided by engineer. */ +"Add contact" = "Додати контакт"; + /* No comment provided by engineer. */ "Add preset servers" = "Додавання попередньо встановлених серверів"; @@ -389,6 +401,9 @@ /* No comment provided by engineer. */ "All group members will remain connected." = "Всі учасники групи залишаться на зв'язку."; +/* No comment provided by engineer. */ +"All messages will be deleted - this cannot be undone!" = "Усі повідомлення будуть видалені - цю дію не можна скасувати!"; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "Всі повідомлення будуть видалені - це неможливо скасувати! Повідомлення будуть видалені ТІЛЬКИ для вас."; @@ -566,15 +581,24 @@ /* No comment provided by engineer. */ "Block" = "Блокувати"; +/* No comment provided by engineer. */ +"Block for all" = "Заблокувати для всіх"; + /* No comment provided by engineer. */ "Block group members" = "Учасники групи блокування"; /* No comment provided by engineer. */ "Block member" = "Заблокувати користувача"; +/* No comment provided by engineer. */ +"Block member for all?" = "Заблокувати учасника для всіх?"; + /* No comment provided by engineer. */ "Block member?" = "Заблокувати користувача?"; +/* No comment provided by engineer. */ +"Blocked by admin" = "Заблокований адміністратором"; + /* No comment provided by engineer. */ "bold" = "жирний"; @@ -614,6 +638,9 @@ /* No comment provided by engineer. */ "Calls" = "Дзвінки"; +/* No comment provided by engineer. */ +"Camera not available" = "Камера недоступна"; + /* No comment provided by engineer. */ "Can't invite contact!" = "Не вдається запросити контакт!"; @@ -699,6 +726,9 @@ /* No comment provided by engineer. */ "Chat is stopped" = "Чат зупинено"; +/* No comment provided by engineer. */ +"Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "Чат зупинено. Якщо ви вже використовували цю базу даних на іншому пристрої, перенесіть її назад перед запуском чату."; + /* No comment provided by engineer. */ "Chat preferences" = "Налаштування чату"; @@ -2127,7 +2157,7 @@ /* copied message info */ "Moderated at: %@" = "Модерується за: %@"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "модерується %@"; /* time unit */ diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 8ed4cb7c7e..8e6458dce6 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -25,6 +25,9 @@ /* No comment provided by engineer. */ "- more stable message delivery.\n- a bit better groups.\n- and more!" = "- 更稳定的传输!\n- 更好的社群!\n- 以及更多!"; +/* No comment provided by engineer. */ +"- optionally notify deleted contacts.\n- profile names with spaces.\n- and more!" = "- 可选择通知已删除的联系人。\n- 带空格的个人资料名称。\n- 以及更多!"; + /* No comment provided by engineer. */ "- voice messages up to 5 minutes.\n- custom time to disappear.\n- editing history." = "- 语音消息最长5分钟。\n- 自定义限时消息。\n- 编辑消息历史。"; @@ -109,12 +112,18 @@ /* No comment provided by engineer. */ "%@ %@" = "%@ %@"; +/* No comment provided by engineer. */ +"%@ and %@" = "%@ 和 %@"; + /* No comment provided by engineer. */ "%@ and %@ connected" = "%@ 和%@ 以建立连接"; /* copied message info, <sender> at <time> */ "%@ at %@:" = "@ %2$@:"; +/* No comment provided by engineer. */ +"%@ connected" = "%@ 已连接"; + /* notification title */ "%@ is connected!" = "%@ 已连接!"; @@ -130,6 +139,9 @@ /* notification title */ "%@ wants to connect!" = "%@ 要连接!"; +/* No comment provided by engineer. */ +"%@, %@ and %lld members" = "%@, %@ 和 %lld 成员"; + /* No comment provided by engineer. */ "%@, %@ and %lld other members connected" = "%@, %@ 和 %lld 个成员"; @@ -169,9 +181,15 @@ /* No comment provided by engineer. */ "%lld file(s) with total size of %@" = "%lld 总文件大小 %@"; +/* No comment provided by engineer. */ +"%lld group events" = "%lld 群组事件"; + /* No comment provided by engineer. */ "%lld members" = "%lld 成员"; +/* No comment provided by engineer. */ +"%lld messages blocked" = "%lld 条消息已屏蔽"; + /* No comment provided by engineer. */ "%lld minutes" = "%lld 分钟"; @@ -305,6 +323,9 @@ /* No comment provided by engineer. */ "Add address to your profile, so that your contacts can share it with other people. Profile update will be sent to your contacts." = "将地址添加到您的个人资料,以便您的联系人可以与其他人共享。个人资料更新将发送给您的联系人。"; +/* No comment provided by engineer. */ +"Add contact" = "添加联系人"; + /* No comment provided by engineer. */ "Add preset servers" = "添加预设服务器"; @@ -356,6 +377,9 @@ /* No comment provided by engineer. */ "All group members will remain connected." = "所有群组成员将保持连接。"; +/* No comment provided by engineer. */ +"All messages will be deleted - this cannot be undone!" = "所有消息都将被删除 - 这无法被撤销!"; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone! The messages will be deleted ONLY for you." = "所有聊天记录和消息将被删除——这一行为无法撤销!只有您的消息会被删除。"; @@ -422,6 +446,12 @@ /* No comment provided by engineer. */ "Already connected?" = "已连接?"; +/* No comment provided by engineer. */ +"Already connecting!" = "已经在连接了!"; + +/* No comment provided by engineer. */ +"Already joining the group!" = "已经加入了该群组!"; + /* pref value */ "always" = "始终"; @@ -488,6 +518,9 @@ /* No comment provided by engineer. */ "Authentication unavailable" = "身份验证不可用"; +/* member role */ +"author" = "作者"; + /* No comment provided by engineer. */ "Auto-accept" = "自动接受"; @@ -500,6 +533,9 @@ /* No comment provided by engineer. */ "Back" = "返回"; +/* No comment provided by engineer. */ +"Bad desktop address" = "糟糕的桌面地址"; + /* integrity error chat item */ "bad message hash" = "错误消息散列"; @@ -512,9 +548,42 @@ /* No comment provided by engineer. */ "Bad message ID" = "错误消息 ID"; +/* No comment provided by engineer. */ +"Better groups" = "更佳的群组"; + /* No comment provided by engineer. */ "Better messages" = "更好的消息"; +/* No comment provided by engineer. */ +"Block" = "封禁"; + +/* No comment provided by engineer. */ +"Block for all" = "为所有人封禁"; + +/* No comment provided by engineer. */ +"Block group members" = "屏蔽群组成员"; + +/* No comment provided by engineer. */ +"Block member" = "封禁成员"; + +/* No comment provided by engineer. */ +"Block member for all?" = "为所有其他成员封禁该成员?"; + +/* No comment provided by engineer. */ +"Block member?" = "封禁成员吗?"; + +/* marked deleted chat item preview text */ +"blocked" = "已封禁"; + +/* rcv group event chat item */ +"blocked %@" = "已封禁 %@"; + +/* marked deleted chat item preview text */ +"blocked by admin" = "由管理员封禁"; + +/* No comment provided by engineer. */ +"Blocked by admin" = "由管理员封禁"; + /* No comment provided by engineer. */ "bold" = "加粗"; @@ -554,6 +623,9 @@ /* No comment provided by engineer. */ "Calls" = "通话"; +/* No comment provided by engineer. */ +"Camera not available" = "相机不可用"; + /* No comment provided by engineer. */ "Can't invite contact!" = "无法邀请联系人!"; @@ -639,6 +711,9 @@ /* No comment provided by engineer. */ "Chat is stopped" = "聊天已停止"; +/* No comment provided by engineer. */ +"Chat is stopped. If you already used this database on another device, you should transfer it back before starting chat." = "聊天已停止。如果你已经在另一台设备商使用过此数据库,你应该在启动聊天前将数据库传输回来。"; + /* No comment provided by engineer. */ "Chat preferences" = "聊天偏好设置"; @@ -666,6 +741,9 @@ /* No comment provided by engineer. */ "Clear conversation?" = "清除对话吗?"; +/* No comment provided by engineer. */ +"Clear private notes?" = "清除私密笔记?"; + /* No comment provided by engineer. */ "Clear verification" = "清除验证"; @@ -705,12 +783,21 @@ /* server test step */ "Connect" = "连接"; +/* No comment provided by engineer. */ +"Connect automatically" = "自动连接"; + /* No comment provided by engineer. */ "Connect incognito" = "在隐身状态下连接"; +/* No comment provided by engineer. */ +"Connect to desktop" = "连接到桌面"; + /* No comment provided by engineer. */ "connect to SimpleX Chat developers." = "连接到 SimpleX Chat 开发者。"; +/* No comment provided by engineer. */ +"Connect to yourself?" = "连接到你自己?"; + /* No comment provided by engineer. */ "Connect via link" = "通过链接连接"; @@ -720,9 +807,15 @@ /* No comment provided by engineer. */ "connected" = "已连接"; +/* No comment provided by engineer. */ +"Connected desktop" = "已连接的桌面"; + /* rcv group event chat item */ "connected directly" = "已直连"; +/* No comment provided by engineer. */ +"Connected to desktop" = "已连接到桌面"; + /* No comment provided by engineer. */ "connecting" = "连接中"; @@ -747,6 +840,9 @@ /* No comment provided by engineer. */ "Connecting server… (error: %@)" = "连接服务器中……(错误:%@)"; +/* No comment provided by engineer. */ +"Connecting to desktop" = "正连接到桌面"; + /* chat list item title */ "connecting…" = "连接中……"; @@ -765,6 +861,9 @@ /* No comment provided by engineer. */ "Connection request sent!" = "已发送连接请求!"; +/* No comment provided by engineer. */ +"Connection terminated" = "连接被终止"; + /* No comment provided by engineer. */ "Connection timeout" = "连接超时"; @@ -816,12 +915,18 @@ /* No comment provided by engineer. */ "Create" = "创建"; +/* No comment provided by engineer. */ +"Create a group using a random profile." = "使用随机身份创建群组"; + /* No comment provided by engineer. */ "Create an address to let people connect with you." = "创建一个地址,让人们与您联系。"; /* server test step */ "Create file" = "创建文件"; +/* No comment provided by engineer. */ +"Create group" = "建群"; + /* No comment provided by engineer. */ "Create group link" = "创建群组链接"; @@ -831,6 +936,9 @@ /* No comment provided by engineer. */ "Create new profile in [desktop app](https://simplex.chat/downloads/). 💻" = "在[桌面应用程序](https://simplex.chat/downloads/)中创建新的个人资料。 💻"; +/* No comment provided by engineer. */ +"Create profile" = "创建个人资料"; + /* server test step */ "Create queue" = "创建队列"; @@ -843,9 +951,15 @@ /* No comment provided by engineer. */ "Create your profile" = "创建您的资料"; +/* No comment provided by engineer. */ +"Created at" = "创建于"; + /* No comment provided by engineer. */ "Created on %@" = "创建于 %@"; +/* No comment provided by engineer. */ +"Creating link…" = "创建链接中…"; + /* No comment provided by engineer. */ "creator" = "创建者"; @@ -957,6 +1071,9 @@ /* No comment provided by engineer. */ "Delete all files" = "删除所有文件"; +/* No comment provided by engineer. */ +"Delete and notify contact" = "删除并通知联系人"; + /* No comment provided by engineer. */ "Delete archive" = "删除档案"; @@ -1053,6 +1170,9 @@ /* copied message info */ "Deleted at: %@" = "已删除于:%@"; +/* rcv direct event chat item */ +"deleted contact" = "已删除联系人"; + /* rcv group event chat item */ "deleted group" = "已删除群组"; @@ -1068,6 +1188,12 @@ /* No comment provided by engineer. */ "Description" = "描述"; +/* No comment provided by engineer. */ +"Desktop address" = "桌面地址"; + +/* No comment provided by engineer. */ +"Desktop devices" = "桌面设备"; + /* No comment provided by engineer. */ "Develop" = "开发"; @@ -1131,12 +1257,21 @@ /* server test step */ "Disconnect" = "断开连接"; +/* No comment provided by engineer. */ +"Disconnect desktop?" = "断开桌面连接?"; + /* No comment provided by engineer. */ "Discover and join groups" = "发现和加入群组"; +/* No comment provided by engineer. */ +"Discover via local network" = "通过本地网络发现"; + /* No comment provided by engineer. */ "Do it later" = "稍后再做"; +/* No comment provided by engineer. */ +"Do not send history to new members." = "不给新成员发送历史消息。"; + /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "请勿使用 SimpleX 进行紧急通话。"; @@ -1182,6 +1317,9 @@ /* No comment provided by engineer. */ "Enable automatic message deletion?" = "启用自动删除消息?"; +/* No comment provided by engineer. */ +"Enable camera access" = "启用相机访问"; + /* No comment provided by engineer. */ "Enable for all" = "全部启用"; @@ -1269,6 +1407,12 @@ /* chat item text */ "encryption re-negotiation allowed for %@" = "允许对 %@ 进行加密重新协商"; +/* message decrypt error item */ +"Encryption re-negotiation error" = "加密重协商错误"; + +/* No comment provided by engineer. */ +"Encryption re-negotiation failed." = "加密重协商失败了。"; + /* chat item text */ "encryption re-negotiation required" = "需要重新进行加密协商"; @@ -1296,6 +1440,9 @@ /* No comment provided by engineer. */ "Enter server manually" = "手动输入服务器"; +/* No comment provided by engineer. */ +"Enter this device name…" = "输入此设备名…"; + /* placeholder */ "Enter welcome message…" = "输入欢迎消息……"; @@ -1341,6 +1488,9 @@ /* No comment provided by engineer. */ "Error creating member contact" = "创建成员联系人时出错"; +/* No comment provided by engineer. */ +"Error creating message" = "创建消息出错"; + /* No comment provided by engineer. */ "Error creating profile!" = "创建资料错误!"; @@ -1473,6 +1623,9 @@ /* No comment provided by engineer. */ "Exit without saving" = "退出而不保存"; +/* chat item action */ +"Expand" = "展开"; + /* No comment provided by engineer. */ "Export database" = "导出数据库"; @@ -1491,6 +1644,9 @@ /* No comment provided by engineer. */ "Fast and no wait until the sender is online!" = "快速且无需等待发件人在线!"; +/* No comment provided by engineer. */ +"Faster joining and more reliable messages." = "加入速度更快、信息更可靠。"; + /* No comment provided by engineer. */ "Favorite" = "最喜欢"; @@ -1548,6 +1704,9 @@ /* No comment provided by engineer. */ "For console" = "用于控制台"; +/* No comment provided by engineer. */ +"Found desktop" = "找到了桌面"; + /* No comment provided by engineer. */ "French interface" = "法语界面"; @@ -1560,6 +1719,9 @@ /* No comment provided by engineer. */ "Full name:" = "全名:"; +/* No comment provided by engineer. */ +"Fully decentralized – visible only to members." = "完全去中心化 - 仅对成员可见。"; + /* No comment provided by engineer. */ "Fully re-implemented - work in background!" = "完全重新实现 - 在后台工作!"; @@ -1572,6 +1734,9 @@ /* No comment provided by engineer. */ "Group" = "群组"; +/* No comment provided by engineer. */ +"Group already exists!" = "群已存在!"; + /* No comment provided by engineer. */ "group deleted" = "群组已删除"; @@ -1671,6 +1836,9 @@ /* No comment provided by engineer. */ "History" = "历史记录"; +/* No comment provided by engineer. */ +"History is not sent to new members." = "未发送历史消息给新成员。"; + /* time unit */ "hours" = "小时"; @@ -1728,6 +1896,9 @@ /* No comment provided by engineer. */ "Import database" = "导入数据库"; +/* No comment provided by engineer. */ +"Improved message delivery" = "改进了消息传递"; + /* No comment provided by engineer. */ "Improved privacy and security" = "改进的隐私和安全"; @@ -1740,6 +1911,9 @@ /* No comment provided by engineer. */ "Incognito" = "隐身聊天"; +/* No comment provided by engineer. */ +"Incognito groups" = "匿名群组"; + /* No comment provided by engineer. */ "Incognito mode" = "隐身模式"; @@ -1767,6 +1941,9 @@ /* No comment provided by engineer. */ "Incompatible database version" = "数据库版本不兼容"; +/* No comment provided by engineer. */ +"Incompatible version" = "不兼容的版本"; + /* PIN entry */ "Incorrect passcode" = "密码错误"; @@ -1806,6 +1983,15 @@ /* invalid chat item */ "invalid data" = "无效数据"; +/* No comment provided by engineer. */ +"Invalid display name!" = "无效的显示名!"; + +/* No comment provided by engineer. */ +"Invalid name!" = "无效名称!"; + +/* No comment provided by engineer. */ +"Invalid QR code" = "无效的二维码"; + /* No comment provided by engineer. */ "Invalid server address!" = "无效的服务器地址!"; @@ -1884,12 +2070,24 @@ /* No comment provided by engineer. */ "Join group" = "加入群组"; +/* No comment provided by engineer. */ +"Join group conversations" = "加入群对话"; + +/* No comment provided by engineer. */ +"Join group?" = "加入群组?"; + /* No comment provided by engineer. */ "Join incognito" = "加入隐身聊天"; /* No comment provided by engineer. */ "Joining group" = "加入群组中"; +/* No comment provided by engineer. */ +"Keep" = "保留"; + +/* No comment provided by engineer. */ +"Keep unused invitation?" = "保留未使用的邀请吗?"; + /* No comment provided by engineer. */ "Keep your connections" = "保持连接"; @@ -1926,6 +2124,15 @@ /* No comment provided by engineer. */ "Limitations" = "限制"; +/* No comment provided by engineer. */ +"Link mobile and desktop apps! 🔗" = "连接移动端和桌面端应用程序!🔗"; + +/* No comment provided by engineer. */ +"Linked desktop options" = "已链接桌面选项"; + +/* No comment provided by engineer. */ +"Linked desktops" = "已链接桌面"; + /* No comment provided by engineer. */ "LIVE" = "实时"; @@ -2067,7 +2274,7 @@ /* copied message info */ "Moderated at: %@" = "已被管理员移除于:%@"; -/* No comment provided by engineer. */ +/* marked deleted chat item preview text */ "moderated by %@" = "由 %@ 审核"; /* time unit */ @@ -2106,6 +2313,9 @@ /* No comment provided by engineer. */ "never" = "从不"; +/* No comment provided by engineer. */ +"New chat" = "新聊天"; + /* notification */ "New contact request" = "新联系人请求"; @@ -2181,6 +2391,9 @@ /* copied message info in history */ "no text" = "无文本"; +/* No comment provided by engineer. */ +"Not compatible!" = "不兼容!"; + /* No comment provided by engineer. */ "Notifications" = "通知"; @@ -2210,6 +2423,9 @@ /* No comment provided by engineer. */ "Ok" = "好的"; +/* No comment provided by engineer. */ +"OK" = "好的"; + /* No comment provided by engineer. */ "Old database" = "旧的数据库"; @@ -2282,6 +2498,9 @@ /* authentication reason */ "Open chat console" = "打开聊天控制台"; +/* No comment provided by engineer. */ +"Open group" = "打开群"; + /* No comment provided by engineer. */ "Open Settings" = "打开设置"; @@ -2291,6 +2510,12 @@ /* No comment provided by engineer. */ "Open-source protocol and code – anybody can run the servers." = "开源协议和代码——任何人都可以运行服务器。"; +/* No comment provided by engineer. */ +"Or scan QR code" = "或者扫描二维码"; + +/* No comment provided by engineer. */ +"Or show this code" = "或者显示此码"; + /* member role */ "owner" = "群主"; @@ -2312,9 +2537,18 @@ /* No comment provided by engineer. */ "Password to show" = "显示密码"; +/* No comment provided by engineer. */ +"Paste desktop address" = "粘贴桌面地址"; + /* No comment provided by engineer. */ "Paste image" = "粘贴图片"; +/* No comment provided by engineer. */ +"Paste link to connect!" = "粘贴链接以连接!"; + +/* No comment provided by engineer. */ +"Paste the link you received" = "粘贴您收到的链接"; + /* No comment provided by engineer. */ "peer-to-peer" = "点对点"; @@ -2396,12 +2630,18 @@ /* No comment provided by engineer. */ "Private filenames" = "私密文件名"; +/* name of notes to self */ +"Private notes" = "私密笔记"; + /* No comment provided by engineer. */ "Profile and server connections" = "资料和服务器连接"; /* No comment provided by engineer. */ "Profile image" = "资料图片"; +/* No comment provided by engineer. */ +"Profile name:" = "显示名:"; + /* No comment provided by engineer. */ "Profile password" = "个人资料密码"; @@ -2462,6 +2702,9 @@ /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address)." = "在 [用户指南](https://simplex.chat/docs/guide/app-settings.html#your-simplex-contact-address) 中阅读更多内容。"; +/* No comment provided by engineer. */ +"Read more in [User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)." = "阅读更多[User Guide](https://simplex.chat/docs/guide/chat-profiles.html#incognito-mode)。"; + /* No comment provided by engineer. */ "Read more in [User Guide](https://simplex.chat/docs/guide/readme.html#connect-to-friends)." = "在 [用户指南](https://simplex.chat/docs/guide/readme.html#connect-to-friends) 中阅读更多内容。"; @@ -2555,6 +2798,12 @@ /* rcv group event chat item */ "removed %@" = "已删除 %@"; +/* profile update event chat item */ +"removed contact address" = "删除了联系地址"; + +/* profile update event chat item */ +"removed profile picture" = "删除了资料图片"; + /* rcv group event chat item */ "removed you" = "已将您移除"; @@ -2567,6 +2816,12 @@ /* No comment provided by engineer. */ "Renegotiate encryption?" = "重新协商加密?"; +/* No comment provided by engineer. */ +"Repeat connection request?" = "重复连接请求吗?"; + +/* No comment provided by engineer. */ +"Repeat join request?" = "重复加入请求吗?"; + /* chat item action */ "Reply" = "回复"; @@ -2600,6 +2855,9 @@ /* No comment provided by engineer. */ "Restore database error" = "恢复数据库错误"; +/* No comment provided by engineer. */ +"Retry" = "重试"; + /* chat item action */ "Reveal" = "揭示"; @@ -2669,6 +2927,9 @@ /* No comment provided by engineer. */ "Save welcome message?" = "保存欢迎信息?"; +/* message info title */ +"Saved message" = "已保存的消息"; + /* No comment provided by engineer. */ "Saved WebRTC ICE servers will be removed" = "已保存的WebRTC ICE服务器将被删除"; @@ -2678,6 +2939,9 @@ /* No comment provided by engineer. */ "Scan QR code" = "扫描二维码"; +/* No comment provided by engineer. */ +"Scan QR code from desktop" = "从桌面扫描二维码"; + /* No comment provided by engineer. */ "Scan security code from your contact's app." = "从您联系人的应用程序中扫描安全码。"; @@ -2687,6 +2951,12 @@ /* No comment provided by engineer. */ "Search" = "搜索"; +/* No comment provided by engineer. */ +"Search bar accepts invitation links." = "搜索栏接受邀请链接。"; + +/* No comment provided by engineer. */ +"Search or paste SimpleX link" = "搜索或粘贴 SimpleX 链接"; + /* network option */ "sec" = "秒"; @@ -2765,6 +3035,9 @@ /* No comment provided by engineer. */ "Send them from gallery or custom keyboards." = "发送它们来自图库或自定义键盘。"; +/* No comment provided by engineer. */ +"Send up to 100 last messages to new members." = "给新成员发送最多 100 条历史消息。"; + /* No comment provided by engineer. */ "Sender cancelled file transfer." = "发送人已取消文件传输。"; @@ -2822,6 +3095,9 @@ /* No comment provided by engineer. */ "Servers" = "服务器"; +/* No comment provided by engineer. */ +"Session code" = "会话码"; + /* No comment provided by engineer. */ "Set 1 day" = "设定1天"; @@ -2834,6 +3110,12 @@ /* No comment provided by engineer. */ "Set it instead of system authentication." = "设置它以代替系统身份验证。"; +/* profile update event chat item */ +"set new contact address" = "设置新的联系地址"; + +/* profile update event chat item */ +"set new profile picture" = "设置新的资料图片"; + /* No comment provided by engineer. */ "Set passcode" = "设置密码"; @@ -2864,6 +3146,9 @@ /* No comment provided by engineer. */ "Share link" = "分享链接"; +/* No comment provided by engineer. */ +"Share this 1-time invite link" = "分享此一次性邀请链接"; + /* No comment provided by engineer. */ "Share with contacts" = "与联系人分享"; @@ -2942,6 +3227,9 @@ /* No comment provided by engineer. */ "Start chat" = "开始聊天"; +/* No comment provided by engineer. */ +"Start chat?" = "启动聊天吗?"; + /* No comment provided by engineer. */ "Start migration" = "开始迁移"; @@ -3002,12 +3290,21 @@ /* No comment provided by engineer. */ "Tap to activate profile." = "点击以激活个人资料。"; +/* No comment provided by engineer. */ +"Tap to Connect" = "轻按连接"; + /* No comment provided by engineer. */ "Tap to join" = "点击加入"; /* No comment provided by engineer. */ "Tap to join incognito" = "点击以加入隐身聊天"; +/* No comment provided by engineer. */ +"Tap to paste link" = "轻按粘贴链接"; + +/* No comment provided by engineer. */ +"Tap to scan" = "轻按扫描"; + /* No comment provided by engineer. */ "Tap to start a new chat" = "点击开始一个新聊天"; @@ -3053,6 +3350,9 @@ /* No comment provided by engineer. */ "The attempt to change database passphrase was not completed." = "更改数据库密码的尝试未完成。"; +/* No comment provided by engineer. */ +"The code you scanned is not a SimpleX link QR code." = "您扫描的码不是 SimpleX 链接的二维码。"; + /* No comment provided by engineer. */ "The connection you accepted will be cancelled!" = "您接受的连接将被取消!"; @@ -3095,6 +3395,9 @@ /* No comment provided by engineer. */ "The servers for new connections of your current chat profile **%@**." = "您当前聊天资料 **%@** 的新连接服务器。"; +/* No comment provided by engineer. */ +"The text you pasted is not a SimpleX link." = "您粘贴的文本不是 SimpleX 链接。"; + /* No comment provided by engineer. */ "Theme" = "主题"; @@ -3116,12 +3419,24 @@ /* notification title */ "this contact" = "这个联系人"; +/* No comment provided by engineer. */ +"This device name" = "此设备名称"; + +/* No comment provided by engineer. */ +"This display name is invalid. Please choose another name." = "显示名无效。请另选一个名称。"; + /* No comment provided by engineer. */ "This group has over %lld members, delivery receipts are not sent." = "该组有超过 %lld 个成员,不发送送货单。"; /* No comment provided by engineer. */ "This group no longer exists." = "该群组已不存在。"; +/* No comment provided by engineer. */ +"This is your own one-time link!" = "这是你自己的一次性链接!"; + +/* No comment provided by engineer. */ +"This is your own SimpleX address!" = "这是你自己的 SimpleX 地址!"; + /* No comment provided by engineer. */ "This setting applies to messages in your current chat profile **%@**." = "此设置适用于您当前聊天资料 **%@** 中的消息。"; @@ -3131,6 +3446,9 @@ /* No comment provided by engineer. */ "To connect, your contact can scan QR code or use the link in the app." = "您的联系人可以扫描二维码或使用应用程序中的链接来建立连接。"; +/* No comment provided by engineer. */ +"To hide unwanted messages." = "隐藏不需要的信息。"; + /* No comment provided by engineer. */ "To make a new connection" = "建立新连接"; @@ -3176,6 +3494,21 @@ /* No comment provided by engineer. */ "Unable to record voice message" = "无法录制语音消息"; +/* No comment provided by engineer. */ +"Unblock" = "解封"; + +/* No comment provided by engineer. */ +"Unblock for all" = "为所有人解封"; + +/* No comment provided by engineer. */ +"Unblock member" = "解封成员"; + +/* No comment provided by engineer. */ +"Unblock member for all?" = "为所有其他成员解封该成员?"; + +/* No comment provided by engineer. */ +"Unblock member?" = "解封成员吗?"; + /* item status description */ "Unexpected error: %@" = "意外错误: %@"; @@ -3209,12 +3542,21 @@ /* No comment provided by engineer. */ "Unknown error" = "未知错误"; +/* No comment provided by engineer. */ +"unknown status" = "未知状态"; + /* No comment provided by engineer. */ "Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions." = "除非您使用 iOS 通话界面,否则请启用请勿打扰模式以避免打扰。"; /* No comment provided by engineer. */ "Unless your contact deleted the connection or this link was already used, it might be a bug - please report it.\nTo connect, please ask your contact to create another connection link and check that you have a stable network connection." = "除非您的联系人已删除此连接或此链接已被使用,否则它可能是一个错误——请报告。\n如果要连接,请让您的联系人创建另一个连接链接,并检查您的网络连接是否稳定。"; +/* No comment provided by engineer. */ +"Unlink" = "取消链接"; + +/* No comment provided by engineer. */ +"Unlink desktop?" = "取消链接桌面端?"; + /* No comment provided by engineer. */ "Unlock" = "解锁"; @@ -3227,6 +3569,9 @@ /* No comment provided by engineer. */ "Unread" = "未读"; +/* No comment provided by engineer. */ +"Up to 100 last messages are sent to new members." = "给新成员发送了最多 100 条历史消息。"; + /* No comment provided by engineer. */ "Update" = "更新"; @@ -3245,6 +3590,9 @@ /* rcv group event chat item */ "updated group profile" = "已更新的群组资料"; +/* profile update event chat item */ +"updated profile" = "更新了资料"; + /* No comment provided by engineer. */ "Updating settings will re-connect the client to all servers." = "更新设置会将客户端重新连接到所有服务器。"; @@ -3269,6 +3617,9 @@ /* No comment provided by engineer. */ "Use for new connections" = "用于新连接"; +/* No comment provided by engineer. */ +"Use from desktop" = "从桌面端使用"; + /* No comment provided by engineer. */ "Use iOS call interface" = "使用 iOS 通话界面"; @@ -3293,9 +3644,18 @@ /* No comment provided by engineer. */ "v%@ (%@)" = "v%@ (%@)"; +/* No comment provided by engineer. */ +"Verify code with desktop" = "用桌面端验证代码"; + +/* No comment provided by engineer. */ +"Verify connection" = "验证连接"; + /* No comment provided by engineer. */ "Verify connection security" = "验证连接安全"; +/* No comment provided by engineer. */ +"Verify connections" = "验证连接"; + /* No comment provided by engineer. */ "Verify security code" = "验证安全码"; @@ -3314,6 +3674,9 @@ /* No comment provided by engineer. */ "via relay" = "通过中继"; +/* No comment provided by engineer. */ +"Via secure quantum resistant protocol." = "通过安全的、抗量子计算机破解的协议。"; + /* No comment provided by engineer. */ "Video call" = "视频通话"; @@ -3332,6 +3695,9 @@ /* No comment provided by engineer. */ "View security code" = "查看安全码"; +/* chat feature */ +"Visible history" = "可见的历史"; + /* No comment provided by engineer. */ "Voice message…" = "语音消息……"; @@ -3392,9 +3758,15 @@ /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "当您与某人共享隐身聊天资料时,该资料将用于他们邀请您加入的群组。"; +/* No comment provided by engineer. */ +"With encrypted files and media." = "加密的文件和媒体。"; + /* No comment provided by engineer. */ "With optional welcome message." = "带有可选的欢迎消息。"; +/* No comment provided by engineer. */ +"With reduced battery usage." = "降低了电量使用。"; + /* No comment provided by engineer. */ "Wrong database passphrase" = "数据库密码错误"; @@ -3422,6 +3794,12 @@ /* No comment provided by engineer. */ "You are already connected to %@." = "您已经连接到 %@。"; +/* No comment provided by engineer. */ +"You are already connecting via this one-time link!" = "你已经在通过这个一次性链接进行连接!"; + +/* No comment provided by engineer. */ +"You are already joining the group via this link." = "你已经在通过此链接加入该群。"; + /* No comment provided by engineer. */ "You are connected to the server used to receive messages from this contact." = "您已连接到用于接收该联系人消息的服务器。"; @@ -3449,6 +3827,9 @@ /* No comment provided by engineer. */ "You can hide or mute a user profile - swipe it to the right." = "您可以隐藏或静音用户个人资料——只需向右滑动。"; +/* No comment provided by engineer. */ +"You can make it visible to your SimpleX contacts via Settings." = "你可以通过设置让它对你的 SimpleX 联系人可见。"; + /* notification body */ "You can now send messages to %@" = "您现在可以给 %@ 发送消息"; @@ -3473,6 +3854,9 @@ /* No comment provided by engineer. */ "You can use markdown to format messages:" = "您可以使用 markdown 来编排消息格式:"; +/* No comment provided by engineer. */ +"You can view invitation link again in connection details." = "您可以在连接详情中再次查看邀请链接。"; + /* No comment provided by engineer. */ "You can't send messages!" = "您无法发送消息!"; @@ -3494,6 +3878,9 @@ /* No comment provided by engineer. */ "You could not be verified; please try again." = "您的身份无法验证,请再试一次。"; +/* No comment provided by engineer. */ +"You have already requested connection via this address!" = "你已经请求通过此地址进行连接!"; + /* No comment provided by engineer. */ "You have no chats" = "您没有聊天记录"; @@ -3545,6 +3932,9 @@ /* No comment provided by engineer. */ "You will be required to authenticate when you start or resume the app after 30 seconds in background." = "当您启动应用或在应用程序驻留后台超过30 秒后,您将需要进行身份验证。"; +/* No comment provided by engineer. */ +"You will connect to all group members." = "你将连接到所有群成员。"; + /* No comment provided by engineer. */ "You will still receive calls and notifications from muted profiles when they are active." = "当静音配置文件处于活动状态时,您仍会收到来自静音配置文件的电话和通知。"; diff --git a/apps/multiplatform/android/src/main/AndroidManifest.xml b/apps/multiplatform/android/src/main/AndroidManifest.xml index d8350ee222..073f1bf8c8 100644 --- a/apps/multiplatform/android/src/main/AndroidManifest.xml +++ b/apps/multiplatform/android/src/main/AndroidManifest.xml @@ -103,11 +103,14 @@ </intent-filter> </activity-alias> - - <activity android:name=".views.call.IncomingCallActivity" + <activity android:name=".views.call.CallActivity" android:showOnLockScreen="true" android:exported="false" - android:launchMode="singleTask"/> + android:launchMode="singleInstance" + android:supportsPictureInPicture="true" + android:autoRemoveFromRecents="true" + android:screenOrientation="portrait" + android:configChanges="screenSize|smallestScreenSize|screenLayout|orientation"/> <provider android:name="androidx.core.content.FileProvider" @@ -133,6 +136,18 @@ android:stopWithTask="false"></service> <!-- SimplexService restart on reboot --> + + <service + android:name=".CallService" + android:enabled="true" + android:exported="false" + android:stopWithTask="false"/> + + <receiver + android:name=".CallService$CallActionReceiver" + android:enabled="true" + android:exported="false" /> + <receiver android:name=".SimplexService$StartReceiver" android:enabled="true" diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/CallService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/CallService.kt new file mode 100644 index 0000000000..f0ddf8f7de --- /dev/null +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/CallService.kt @@ -0,0 +1,176 @@ +package chat.simplex.app + +import android.app.* +import android.content.* +import android.graphics.Bitmap +import android.graphics.BitmapFactory +import android.os.* +import androidx.compose.ui.graphics.asAndroidBitmap +import androidx.core.app.NotificationCompat +import androidx.core.content.ContextCompat +import chat.simplex.app.model.NtfManager.EndCallAction +import chat.simplex.app.views.call.CallActivity +import chat.simplex.common.model.NotificationPreviewMode +import chat.simplex.common.platform.* +import chat.simplex.common.views.call.CallState +import chat.simplex.common.views.helpers.* +import chat.simplex.res.MR +import kotlinx.datetime.Instant + +class CallService: Service() { + private var wakeLock: PowerManager.WakeLock? = null + private var notificationManager: NotificationManager? = null + private var serviceNotification: Notification? = null + + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { + Log.d(TAG, "onStartCommand startId: $startId") + if (intent != null) { + val action = intent.action + Log.d(TAG, "intent action $action") + when (action) { + Action.START.name -> startService() + else -> Log.e(TAG, "No action in the intent") + } + } else { + Log.d(TAG, "null intent. Probably restarted by the system.") + } + startForeground(CALL_SERVICE_ID, serviceNotification) + return START_STICKY + } + + override fun onCreate() { + super.onCreate() + Log.d(TAG, "Call service created") + notificationManager = createNotificationChannel() + updateNotification() + startForeground(CALL_SERVICE_ID, serviceNotification) + } + + override fun onDestroy() { + Log.d(TAG, "Call service destroyed") + try { + wakeLock?.let { + while (it.isHeld) it.release() // release all, in case acquired more than once + } + wakeLock = null + } catch (e: Exception) { + Log.d(TAG, "Exception while releasing wakelock: ${e.message}") + } + super.onDestroy() + } + + private fun startService() { + Log.d(TAG, "CallService startService") + if (wakeLock != null) return + wakeLock = (getSystemService(Context.POWER_SERVICE) as PowerManager).run { + newWakeLock(PowerManager.PARTIAL_WAKE_LOCK, WAKE_LOCK_TAG).apply { + acquire() + } + } + } + + fun updateNotification() { + val call = chatModel.activeCall.value + val previewMode = appPreferences.notificationPreviewMode.get() + val title = if (previewMode == NotificationPreviewMode.HIDDEN.name) + generalGetString(MR.strings.notification_preview_somebody) + else + call?.contact?.profile?.displayName ?: "" + val text = generalGetString(if (call?.supportsVideo() == true) MR.strings.call_service_notification_video_call else MR.strings.call_service_notification_audio_call) + val image = call?.contact?.image + val largeIcon = if (image == null || previewMode == NotificationPreviewMode.HIDDEN.name) + BitmapFactory.decodeResource(resources, R.drawable.icon) + else + base64ToBitmap(image).asAndroidBitmap() + + serviceNotification = createNotification(title, text, largeIcon, call?.connectedAt) + startForeground(CALL_SERVICE_ID, serviceNotification) + } + + private fun createNotificationChannel(): NotificationManager? { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { + val notificationManager = getSystemService(Context.NOTIFICATION_SERVICE) as NotificationManager + val channel = NotificationChannel(CALL_NOTIFICATION_CHANNEL_ID, CALL_NOTIFICATION_CHANNEL_NAME, NotificationManager.IMPORTANCE_DEFAULT) + notificationManager.createNotificationChannel(channel) + return notificationManager + } + return null + } + + private fun createNotification(title: String, text: String, icon: Bitmap, connectedAt: Instant? = null): Notification { + val pendingIntent: PendingIntent = Intent(this, CallActivity::class.java).let { notificationIntent -> + PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE) + } + + val endCallPendingIntent: PendingIntent = Intent(this, CallActionReceiver::class.java).let { notificationIntent -> + notificationIntent.setAction(EndCallAction) + PendingIntent.getBroadcast(this, 1, notificationIntent, PendingIntent.FLAG_CANCEL_CURRENT or PendingIntent.FLAG_IMMUTABLE) + } + + val builder = NotificationCompat.Builder(this, CALL_NOTIFICATION_CHANNEL_ID) + .setSmallIcon(R.drawable.ntf_icon) + .setLargeIcon(icon.clipToCircle()) + .setColor(0x88FFFF) + .setContentTitle(title) + .setContentText(text) + .setContentIntent(pendingIntent) + .setSilent(true) + .addAction(R.drawable.ntf_icon, generalGetString(MR.strings.call_service_notification_end_call), endCallPendingIntent) + if (connectedAt != null) { + builder.setUsesChronometer(true) + builder.setWhen(connectedAt.epochSeconds * 1000) + } + + return builder.build() + } + + override fun onBind(intent: Intent): IBinder { + return CallServiceBinder() + } + + inner class CallServiceBinder : Binder() { + fun getService() = this@CallService + } + + enum class Action { + START, + } + + class CallActionReceiver: BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + when (intent?.action) { + EndCallAction -> { + val call = chatModel.activeCall.value + if (call != null) { + withBGApi { + chatModel.callManager.endCall(call) + } + } + } + else -> { + Log.e(TAG, "Unknown action. Make sure you provided an action") + } + } + } + } + +companion object { + const val TAG = "CALL_SERVICE" + const val CALL_NOTIFICATION_CHANNEL_ID = "chat.simplex.app.CALL_SERVICE_NOTIFICATION" + const val CALL_NOTIFICATION_CHANNEL_NAME = "SimpleX Chat call service" + const val CALL_SERVICE_ID = 6788 + const val WAKE_LOCK_TAG = "CallService::lock" + + fun startService(): Intent { + Log.d(TAG, "CallService start") + return Intent(androidAppContext, CallService::class.java).also { + it.action = Action.START.name + ContextCompat.startForegroundService(androidAppContext, it) + } + } + + fun stopService() { + androidAppContext.stopService(Intent(androidAppContext, CallService::class.java)) + } + } +} diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt index 51cf1ba4ca..f29aa39607 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexApp.kt @@ -1,14 +1,21 @@ package chat.simplex.app -import android.app.Application +import android.annotation.SuppressLint +import android.app.* import android.content.Context -import androidx.compose.ui.platform.ClipboardManager import chat.simplex.common.platform.Log -import android.app.UiModeManager +import android.content.Intent +import android.content.pm.ActivityInfo +import android.media.AudioManager import android.os.* +import androidx.compose.runtime.Composable +import androidx.compose.runtime.DisposableEffect +import androidx.compose.ui.platform.LocalContext import androidx.lifecycle.* import androidx.work.* import chat.simplex.app.model.NtfManager +import chat.simplex.app.model.NtfManager.AcceptCallAction +import chat.simplex.app.views.call.CallActivity import chat.simplex.common.helpers.APPLICATION_ID import chat.simplex.common.helpers.requiresIgnoringBattery import chat.simplex.common.model.* @@ -17,7 +24,7 @@ import chat.simplex.common.model.ChatModel.updatingChatsMutex import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.CurrentColors import chat.simplex.common.ui.theme.DefaultTheme -import chat.simplex.common.views.call.RcvCallInvitation +import chat.simplex.common.views.call.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.onboarding.OnboardingStage import com.jakewharton.processphoenix.ProcessPhoenix @@ -63,7 +70,11 @@ class SimplexApp: Application(), LifecycleEventObserver { tmpDir.deleteRecursively() tmpDir.mkdir() - if (DatabaseUtils.ksSelfDestructPassword.get() == null) { + // Present screen for continue migration if it wasn't finished yet + if (chatModel.migrationState.value != null) { + // It's important, otherwise, user may be locked in undefined state + appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) + } else if (DatabaseUtils.ksAppPassword.get() == null || DatabaseUtils.ksSelfDestructPassword.get() == null) { initChatControllerAndRunMigrations() } ProcessLifecycleOwner.get().lifecycle.addObserver(this@SimplexApp) @@ -184,6 +195,10 @@ class SimplexApp: Application(), LifecycleEventObserver { SimplexService.safeStopService() } + override fun androidCallServiceSafeStop() { + CallService.stopService() + } + override fun androidNotificationsModeChanged(mode: NotificationsMode) { if (mode.requiresIgnoringBattery && !SimplexService.isBackgroundAllowed()) { appPrefs.backgroundServiceNoticeShown.set(false) @@ -254,6 +269,43 @@ class SimplexApp: Application(), LifecycleEventObserver { uiModeManager.setApplicationNightMode(mode) } + override fun androidStartCallActivity(acceptCall: Boolean, remoteHostId: Long?, chatId: ChatId?) { + val context = mainActivity.get() ?: return + val intent = Intent(context, CallActivity::class.java) + .addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION) + if (acceptCall) { + intent.setAction(AcceptCallAction) + .putExtra("remoteHostId", remoteHostId) + .putExtra("chatId", chatId) + } + intent.flags += Intent.FLAG_ACTIVITY_BROUGHT_TO_FRONT + context.startActivity(intent) + } + + override fun androidPictureInPictureAllowed(): Boolean { + val appOps = androidAppContext.getSystemService(Context.APP_OPS_SERVICE) as AppOpsManager + return appOps.checkOpNoThrow(AppOpsManager.OPSTR_PICTURE_IN_PICTURE, Process.myUid(), packageName) == AppOpsManager.MODE_ALLOWED + } + + override fun androidCallEnded() { + activeCallDestroyWebView() + } + + @SuppressLint("SourceLockedOrientationActivity") + @Composable + override fun androidLockPortraitOrientation() { + val context = LocalContext.current + DisposableEffect(Unit) { + val activity = context as? Activity ?: return@DisposableEffect onDispose {} + // Lock orientation to portrait in order to have good experience with calls + activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT + onDispose { + // Unlock orientation + activity.requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_UNSPECIFIED + } + } + } + override suspend fun androidAskToAllowBackgroundCalls(): Boolean { if (SimplexService.isBackgroundRestricted()) { val userChoice: CompletableDeferred<Boolean> = CompletableDeferred() diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt index 903f096080..b0600e6d59 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/SimplexService.kt @@ -34,12 +34,13 @@ import kotlin.system.exitProcess class SimplexService: Service() { private var wakeLock: PowerManager.WakeLock? = null - private var isStartingService = false + private var isCheckingNewMessages = false private var notificationManager: NotificationManager? = null private var serviceNotification: Notification? = null override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { Log.d(TAG, "onStartCommand startId: $startId") + isServiceStarting = false if (intent != null) { val action = intent.action Log.d(TAG, "intent action $action") @@ -71,6 +72,7 @@ class SimplexService: Service() { stopForeground(true) stopSelf() } else { + isServiceStarting = false isServiceStarted = true // In case of self-destruct is enabled the initialization process will not start in SimplexApp, Let's start it here if (DatabaseUtils.ksSelfDestructPassword.get() != null && chatModel.chatDbStatus.value == null) { @@ -89,6 +91,7 @@ class SimplexService: Service() { } catch (e: Exception) { Log.d(TAG, "Exception while releasing wakelock: ${e.message}") } + isServiceStarting = false isServiceStarted = false stopAfterStart = false saveServiceState(this, ServiceState.STOPPED) @@ -101,9 +104,9 @@ class SimplexService: Service() { private fun startService() { Log.d(TAG, "SimplexService startService") - if (wakeLock != null || isStartingService) return + if (wakeLock != null || isCheckingNewMessages) return val self = this - isStartingService = true + isCheckingNewMessages = true withLongRunningApi { val chatController = ChatController waitDbMigrationEnds(chatController) @@ -123,7 +126,7 @@ class SimplexService: Service() { } } } finally { - isStartingService = false + isCheckingNewMessages = false } } } @@ -262,6 +265,7 @@ class SimplexService: Service() { private const val SHARED_PREFS_SERVICE_STATE = "SIMPLEX_SERVICE_STATE" private const val WORK_NAME_ONCE = "ServiceStartWorkerOnce" + var isServiceStarting = false var isServiceStarted = false private var stopAfterStart = false @@ -281,7 +285,7 @@ class SimplexService: Service() { fun safeStopService() { if (isServiceStarted) { androidAppContext.stopService(Intent(androidAppContext, SimplexService::class.java)) - } else { + } else if (isServiceStarting) { stopAfterStart = true } } @@ -291,6 +295,7 @@ class SimplexService: Service() { withContext(Dispatchers.IO) { Intent(androidAppContext, SimplexService::class.java).also { it.action = action.name + isServiceStarting = true ContextCompat.startForegroundService(androidAppContext, it) } } diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt index d32508c7b0..7673658cfb 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/model/NtfManager.android.kt @@ -13,7 +13,7 @@ import androidx.compose.ui.graphics.asAndroidBitmap import androidx.core.app.* import chat.simplex.app.* import chat.simplex.app.TAG -import chat.simplex.app.views.call.IncomingCallActivity +import chat.simplex.app.views.call.CallActivity import chat.simplex.app.views.call.getKeyguardManager import chat.simplex.common.views.helpers.* import chat.simplex.common.model.* @@ -33,6 +33,7 @@ object NtfManager { const val CallChannel: String = "chat.simplex.app.CALL_NOTIFICATION_2" const val AcceptCallAction: String = "chat.simplex.app.ACCEPT_CALL" const val RejectCallAction: String = "chat.simplex.app.REJECT_CALL" + const val EndCallAction: String = "chat.simplex.app.END_CALL" const val CallNotificationId: Int = -1 private const val UserIdKey: String = "userId" private const val ChatIdKey: String = "chatId" @@ -157,7 +158,7 @@ object NtfManager { val screenOff = displayManager.displays.all { it.state != Display.STATE_ON } var ntfBuilder = if ((keyguardManager.isKeyguardLocked || screenOff) && appPreferences.callOnLockScreen.get() != CallOnLockScreen.DISABLE) { - val fullScreenIntent = Intent(context, IncomingCallActivity::class.java) + val fullScreenIntent = Intent(context, CallActivity::class.java) val fullScreenPendingIntent = PendingIntent.getActivity(context, 0, fullScreenIntent, PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_IMMUTABLE) NotificationCompat.Builder(context, CallChannel) .setFullScreenIntent(fullScreenPendingIntent, true) diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt similarity index 53% rename from apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt rename to apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt index d09cb019f8..6b1b01db99 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/IncomingCallActivity.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/views/call/CallActivity.kt @@ -1,17 +1,18 @@ package chat.simplex.app.views.call -import android.app.Activity -import android.app.KeyguardManager -import android.content.Context -import android.content.Intent -import android.os.Build -import android.os.Bundle -import chat.simplex.common.platform.Log -import android.view.WindowManager +import android.app.* +import android.content.* +import android.content.res.Configuration +import android.graphics.Rect +import android.os.* +import android.util.Rational +import android.view.* import androidx.activity.ComponentActivity import androidx.activity.compose.setContent +import androidx.activity.trackPipAnimationHintView import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.Image +import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* @@ -22,33 +23,115 @@ 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.res.painterResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp +import androidx.lifecycle.Lifecycle import chat.simplex.app.* 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.model.* -import chat.simplex.app.model.NtfManager.OpenChatAction -import chat.simplex.common.platform.ntfManager +import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.* import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.launch import kotlinx.datetime.Clock +import java.lang.ref.WeakReference +import chat.simplex.common.platform.chatModel as m -class IncomingCallActivity: ComponentActivity() { +class CallActivity: ComponentActivity(), ServiceConnection { + + var boundService: CallService? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - setContent { IncomingCallActivityView(ChatModel) } - unlockForIncomingCall() + callActivity = WeakReference(this) + when (intent?.action) { + AcceptCallAction -> { + val remoteHostId = intent.getLongExtra("remoteHostId", -1).takeIf { it != -1L } + val chatId = intent.getStringExtra("chatId") + val invitation = (m.callInvitations.values + m.activeCallInvitation.value).lastOrNull { + it?.remoteHostId == remoteHostId && it?.contact?.id == chatId + } + if (invitation != null) { + m.callManager.acceptIncomingCall(invitation = invitation) + } + } + } + + setContent { CallActivityView() } + + if (isOnLockScreenNow()) { + unlockForIncomingCall() + } } override fun onDestroy() { super.onDestroy() - lockAfterIncomingCall() + if (isOnLockScreenNow()) { + lockAfterIncomingCall() + } + try { + unbindService(this) + } catch (e: Exception) { + Log.i(TAG, "Unable to unbind service: " + e.stackTraceToString()) + } + } + + private fun isOnLockScreenNow() = getKeyguardManager(this).isKeyguardLocked + + fun setPipParams(video: Boolean, sourceRectHint: Rect? = null, viewRatio: Rational? = null) { + // By manually specifying source rect we exclude empty background while toggling PiP + val builder = PictureInPictureParams.Builder() + .setAspectRatio(viewRatio) + .setSourceRectHint(sourceRectHint) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.S) { + builder.setAutoEnterEnabled(video) + } + setPictureInPictureParams(builder.build()) + } + + override fun onPictureInPictureModeChanged(isInPictureInPictureMode: Boolean, newConfig: Configuration) { + super.onPictureInPictureModeChanged(isInPictureInPictureMode, newConfig) + m.activeCallViewIsCollapsed.value = isInPictureInPictureMode + val layoutType = if (!isInPictureInPictureMode) { + LayoutType.Default + } else { + LayoutType.RemoteVideo + } + m.callCommand.add(WCallCommand.Layout(layoutType)) + } + + override fun onBackPressed() { + if (isOnLockScreenNow()) { + super.onBackPressed() + } else { + m.activeCallViewIsCollapsed.value = true + } + } + + override fun onPictureInPictureRequested(): Boolean { + Log.d(TAG, "Requested picture-in-picture from the system") + return super.onPictureInPictureRequested() + } + + override fun onUserLeaveHint() { + // On Android 12+ PiP is enabled automatically when a user hides the app + if (Build.VERSION.SDK_INT <= Build.VERSION_CODES.R && callSupportsVideo() && platform.androidPictureInPictureAllowed()) { + enterPictureInPictureMode() + } + } + + override fun onResume() { + super.onResume() + m.activeCallViewIsCollapsed.value = false } private fun unlockForIncomingCall() { @@ -72,6 +155,23 @@ class IncomingCallActivity: ComponentActivity() { } } + fun startServiceAndBind() { + /** + * On Android 12 there is a bug that prevents starting activity after pressing back button + * (the error says that it denies to start activity in background). + * Workaround is to bind to a service + * */ + bindService(CallService.startService(), this, 0) + } + + override fun onServiceConnected(name: ComponentName?, service: IBinder?) { + boundService = (service as CallService.CallServiceBinder).getService() + } + + override fun onServiceDisconnected(name: ComponentName?) { + boundService = null + } + companion object { const val activityFlags = WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON or WindowManager.LayoutParams.FLAG_ALLOW_LOCK_WHILE_SCREEN_ON } @@ -80,38 +180,96 @@ class IncomingCallActivity: ComponentActivity() { fun getKeyguardManager(context: Context): KeyguardManager = context.getSystemService(Context.KEYGUARD_SERVICE) as KeyguardManager +private fun callSupportsVideo() = m.activeCall.value?.supportsVideo() == true || m.activeCallInvitation.value?.callType?.media == CallMediaType.Video + @Composable -fun IncomingCallActivityView(m: ChatModel) { +fun CallActivityView() { val switchingCall = m.switchingCall.value val invitation = m.activeCallInvitation.value - val call = m.activeCall.value + val call = remember { m.activeCall }.value val showCallView = m.showCallView.value - val activity = LocalContext.current as Activity - LaunchedEffect(invitation, call, switchingCall, showCallView) { - if (!switchingCall && invitation == null && (!showCallView || call == null)) { - Log.d(TAG, "IncomingCallActivityView: finishing activity") - activity.finish() - } + val activity = LocalContext.current as CallActivity + LaunchedEffect(Unit) { + snapshotFlow { m.activeCallViewIsCollapsed.value } + .collect { collapsed -> + when { + collapsed -> { + if (!platform.androidPictureInPictureAllowed() || !callSupportsVideo()) { + activity.moveTaskToBack(true) + activity.startActivity(Intent(activity, MainActivity::class.java)) + } else if (!activity.isInPictureInPictureMode && activity.lifecycle.currentState == Lifecycle.State.RESUMED) { + // User pressed back button, show MainActivity + activity.startActivity(Intent(activity, MainActivity::class.java)) + activity.enterPictureInPictureMode() + } + } + callSupportsVideo() && !platform.androidPictureInPictureAllowed() -> { + // PiP disabled by user + platform.androidStartCallActivity(false) + } + activity.isInPictureInPictureMode -> { + platform.androidStartCallActivity(false) + } + } + } } SimpleXTheme { - Surface( - Modifier - .fillMaxSize(), - color = MaterialTheme.colors.background, - contentColor = LocalContentColor.current - ) { - if (showCallView) { - Box { - ActiveCallView() - if (invitation != null) IncomingCallAlertView(invitation, m) + var prevCall by remember { mutableStateOf(call) } + KeyChangeEffect(m.activeCall.value) { + if (m.activeCall.value != null) { + prevCall = m.activeCall.value + activity.boundService?.updateNotification() + } + } + Box(Modifier.background(Color.Black)) { + if (call != null) { + val view = LocalView.current + ActiveCallView() + if (callSupportsVideo()) { + val scope = rememberCoroutineScope() + LaunchedEffect(Unit) { + scope.launch { + activity.setPipParams(callSupportsVideo(), viewRatio = Rational(view.width, view.height)) + activity.trackPipAnimationHintView(view) + } + } + } + } else if (prevCall != null) { + prevCall?.let { ActiveCallOverlayDisabled(it) } + } + if (invitation != null) { + if (call == null) { + Surface( + Modifier + .fillMaxSize(), + color = MaterialTheme.colors.background, + contentColor = LocalContentColor.current + ) { + IncomingCallLockScreenAlert(invitation, m) + } + } else { + IncomingCallAlertView(invitation, m) } - } else if (invitation != null) { - IncomingCallLockScreenAlert(invitation, m) } } } + LaunchedEffect(call == null) { + if (call != null) { + activity.startServiceAndBind() + } + } + LaunchedEffect(invitation, call, switchingCall, showCallView) { + if (!switchingCall && invitation == null && (!showCallView || call == null)) { + Log.d(TAG, "CallActivityView: finishing activity") + activity.finish() + } + } } +/** +* Related to lockscreen +* */ + @Composable fun IncomingCallLockScreenAlert(invitation: RcvCallInvitation, chatModel: ChatModel) { val cm = chatModel.callManager @@ -135,7 +293,7 @@ fun IncomingCallLockScreenAlert(invitation: RcvCallInvitation, chatModel: ChatMo acceptCall = { cm.acceptIncomingCall(invitation = invitation) }, openApp = { val intent = Intent(context, MainActivity::class.java) - .setAction(OpenChatAction) + .setAction(NtfManager.OpenChatAction) .putExtra("userId", invitation.user.userId) .putExtra("chatId", invitation.contact.id) context.startActivity(intent) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt index 90b18bde93..547db51bad 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt @@ -4,6 +4,7 @@ import android.annotation.SuppressLint import android.content.Context import android.net.LocalServerSocket import android.util.Log +import androidx.activity.ComponentActivity import androidx.fragment.app.FragmentActivity import chat.simplex.common.* import chat.simplex.common.platform.* @@ -25,7 +26,8 @@ val defaultLocale: Locale = Locale.getDefault() @SuppressLint("StaticFieldLeak") lateinit var androidAppContext: Context -lateinit var mainActivity: WeakReference<FragmentActivity> +var mainActivity: WeakReference<FragmentActivity> = WeakReference(null) +var callActivity: WeakReference<ComponentActivity> = WeakReference(null) fun initHaskell() { val socketName = "chat.simplex.app.local.socket.address.listen.native.cmd2" + Random.nextLong(100000) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt index 832f0d9cbb..fc323f6ffd 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Images.android.kt @@ -61,6 +61,16 @@ actual fun cropToSquare(image: ImageBitmap): ImageBitmap { return Bitmap.createBitmap(image.asAndroidBitmap(), xOffset, yOffset, side, side).asImageBitmap() } +fun Bitmap.clipToCircle(): Bitmap { + val circle = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888) + val path = android.graphics.Path() + path.addCircle(width / 2f, height / 2f, min(width, height) / 2f, android.graphics.Path.Direction.CCW) + val canvas = android.graphics.Canvas(circle) + canvas.clipPath(path) + canvas.drawBitmap(this, 0f, 0f, null) + return circle +} + actual fun compressImageStr(bitmap: ImageBitmap): String { val usePng = bitmap.hasAlpha() val ext = if (usePng) "png" else "jpg" diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt new file mode 100644 index 0000000000..199e719703 --- /dev/null +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/ScrollableColumn.android.kt @@ -0,0 +1,35 @@ +package chat.simplex.common.platform + +import androidx.compose.foundation.* +import androidx.compose.foundation.gestures.FlingBehavior +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.* +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier + +@Composable +actual fun LazyColumnWithScrollBar( + modifier: Modifier, + state: LazyListState, + contentPadding: PaddingValues, + reverseLayout: Boolean, + verticalArrangement: Arrangement.Vertical, + horizontalAlignment: Alignment.Horizontal, + flingBehavior: FlingBehavior, + userScrollEnabled: Boolean, + content: LazyListScope.() -> Unit +) { + LazyColumn(modifier, state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) +} + +@Composable +actual fun ColumnWithScrollBar( + modifier: Modifier, + verticalArrangement: Arrangement.Vertical, + horizontalAlignment: Alignment.Horizontal, + state: ScrollState, + content: @Composable ColumnScope.() -> Unit +) { + Column(modifier.verticalScroll(rememberScrollState()), verticalArrangement, horizontalAlignment, content) +} diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt index ac14b5199f..8a958327d8 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/call/CallView.android.kt @@ -28,6 +28,7 @@ import androidx.compose.ui.platform.LocalLifecycleOwner import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.viewinterop.AndroidView import androidx.lifecycle.Lifecycle @@ -50,20 +51,30 @@ import kotlinx.datetime.Clock import kotlinx.serialization.decodeFromString import kotlinx.serialization.encodeToString +// Should be destroy()'ed and set as null when call is ended. Otherwise, it will be a leak +@SuppressLint("StaticFieldLeak") +private var staticWebView: WebView? = null + +// WebView methods must be called on Main thread +fun activeCallDestroyWebView() = withApi { + // Stop it when call ended + platform.androidCallServiceSafeStop() + staticWebView?.destroy() + staticWebView = null + Log.d(TAG, "CallView: webview was destroyed") +} + @SuppressLint("SourceLockedOrientationActivity") @Composable actual fun ActiveCallView() { - val chatModel = ChatModel - BackHandler(onBack = { - val call = chatModel.activeCall.value - if (call != null) withBGApi { chatModel.callManager.endCall(call) } - }) val audioViaBluetooth = rememberSaveable { mutableStateOf(false) } - val ntfModeService = remember { chatModel.controller.appPrefs.notificationsMode.get() == NotificationsMode.SERVICE } - LaunchedEffect(Unit) { - // Start service when call happening since it's not already started. - // It's needed to prevent Android from shutting down a microphone after a minute or so when screen is off - if (!ntfModeService) platform.androidServiceStart() + val proximityLock = remember { + val pm = (androidAppContext.getSystemService(Context.POWER_SERVICE) as PowerManager) + if (pm.isWakeLockLevelSupported(PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { + pm.newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, androidAppContext.packageName + ":proximityLock") + } else { + null + } } DisposableEffect(Unit) { val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager @@ -93,22 +104,24 @@ actual fun ActiveCallView() { } } am.registerAudioDeviceCallback(audioCallback, null) - val pm = (androidAppContext.getSystemService(Context.POWER_SERVICE) as PowerManager) - val proximityLock = if (pm.isWakeLockLevelSupported(PROXIMITY_SCREEN_OFF_WAKE_LOCK)) { - pm.newWakeLock(PROXIMITY_SCREEN_OFF_WAKE_LOCK, androidAppContext.packageName + ":proximityLock") - } else { - null - } - proximityLock?.acquire() onDispose { - // Stop it when call ended - if (!ntfModeService) platform.androidServiceSafeStop() dropAudioManagerOverrides() am.unregisterAudioDeviceCallback(audioCallback) - proximityLock?.release() + if (proximityLock?.isHeld == true) { + proximityLock.release() + } + } + } + LaunchedEffect(chatModel.activeCallViewIsCollapsed.value) { + if (chatModel.activeCallViewIsCollapsed.value) { + if (proximityLock?.isHeld == true) proximityLock.release() + } else { + delay(1000) + if (proximityLock?.isHeld == false) proximityLock.acquire() } } val scope = rememberCoroutineScope() + val call = chatModel.activeCall.value Box(Modifier.fillMaxSize()) { WebRTCView(chatModel.callCommand) { apiMsg -> Log.d(TAG, "received from WebRTCView: $apiMsg") @@ -120,15 +133,15 @@ actual fun ActiveCallView() { is WCallResponse.Capabilities -> withBGApi { val callType = CallType(call.localMedia, r.capabilities) chatModel.controller.apiSendCallInvitation(callRh, call.contact, callType) - chatModel.activeCall.value = call.copy(callState = CallState.InvitationSent, localCapabilities = r.capabilities) + updateActiveCall(call) { it.copy(callState = CallState.InvitationSent, localCapabilities = r.capabilities) } } is WCallResponse.Offer -> withBGApi { chatModel.controller.apiSendCallOffer(callRh, call.contact, r.offer, r.iceCandidates, call.localMedia, r.capabilities) - chatModel.activeCall.value = call.copy(callState = CallState.OfferSent, localCapabilities = r.capabilities) + updateActiveCall(call) { it.copy(callState = CallState.OfferSent, localCapabilities = r.capabilities) } } is WCallResponse.Answer -> withBGApi { chatModel.controller.apiSendCallAnswer(callRh, call.contact, r.answer, r.iceCandidates) - chatModel.activeCall.value = call.copy(callState = CallState.Negotiated) + updateActiveCall(call) { it.copy(callState = CallState.Negotiated) } } is WCallResponse.Ice -> withBGApi { chatModel.controller.apiSendCallExtraInfo(callRh, call.contact, r.iceCandidates) @@ -137,15 +150,15 @@ actual fun ActiveCallView() { try { val callStatus = json.decodeFromString<WebRTCCallStatus>("\"${r.state.connectionState}\"") if (callStatus == WebRTCCallStatus.Connected) { - chatModel.activeCall.value = call.copy(callState = CallState.Connected, connectedAt = Clock.System.now()) + updateActiveCall(call) { it.copy(callState = CallState.Connected, connectedAt = Clock.System.now()) } setCallSound(call.soundSpeaker, audioViaBluetooth) } withBGApi { chatModel.controller.apiCallStatus(callRh, call.contact, callStatus) } - } catch (e: Error) { + } catch (e: Throwable) { Log.d(TAG,"call status ${r.state.connectionState} not used") } is WCallResponse.Connected -> { - chatModel.activeCall.value = call.copy(callState = CallState.Connected, connectionInfo = r.connectionInfo) + updateActiveCall(call) { it.copy(callState = CallState.Connected, connectionInfo = r.connectionInfo) } scope.launch { setCallSound(call.soundSpeaker, audioViaBluetooth) } @@ -154,27 +167,29 @@ actual fun ActiveCallView() { withBGApi { chatModel.callManager.endCall(call) } } is WCallResponse.Ended -> { - chatModel.activeCall.value = call.copy(callState = CallState.Ended) + updateActiveCall(call) { it.copy(callState = CallState.Ended) } withBGApi { chatModel.callManager.endCall(call) } - chatModel.showCallView.value = false } is WCallResponse.Ok -> when (val cmd = apiMsg.command) { is WCallCommand.Answer -> - chatModel.activeCall.value = call.copy(callState = CallState.Negotiated) + updateActiveCall(call) { it.copy(callState = CallState.Negotiated) } is WCallCommand.Media -> { - when (cmd.media) { - CallMediaType.Video -> chatModel.activeCall.value = call.copy(videoEnabled = cmd.enable) - CallMediaType.Audio -> chatModel.activeCall.value = call.copy(audioEnabled = cmd.enable) + updateActiveCall(call) { + when (cmd.media) { + CallMediaType.Video -> it.copy(videoEnabled = cmd.enable) + CallMediaType.Audio -> it.copy(audioEnabled = cmd.enable) + } } } is WCallCommand.Camera -> { - chatModel.activeCall.value = call.copy(localCamera = cmd.camera) + updateActiveCall(call) { it.copy(localCamera = cmd.camera) } if (!call.audioEnabled) { chatModel.callCommand.add(WCallCommand.Media(CallMediaType.Audio, enable = false)) } } - is WCallCommand.End -> - chatModel.showCallView.value = false + is WCallCommand.End -> { + withBGApi { chatModel.callManager.endCall(call) } + } else -> {} } is WCallResponse.Error -> { @@ -183,8 +198,16 @@ actual fun ActiveCallView() { } } } - val call = chatModel.activeCall.value - if (call != null) ActiveCallOverlay(call, chatModel, audioViaBluetooth) + val showOverlay = when { + call == null -> false + !platform.androidPictureInPictureAllowed() -> true + !call.supportsVideo() -> true + !chatModel.activeCallViewIsCollapsed.value -> true + else -> false + } + if (call != null && showOverlay) { + ActiveCallOverlay(call, chatModel, audioViaBluetooth) + } } val context = LocalContext.current @@ -229,6 +252,20 @@ private fun ActiveCallOverlay(call: Call, chatModel: ChatModel, audioViaBluetoot ) } +@Composable +fun ActiveCallOverlayDisabled(call: Call) { + ActiveCallOverlayLayout( + call = call, + speakerCanBeEnabled = false, + enabled = false, + dismiss = {}, + toggleAudio = {}, + toggleVideo = {}, + toggleSound = {}, + flipCamera = {} + ) +} + private fun setCallSound(speaker: Boolean, audioViaBluetooth: MutableState<Boolean>) { val am = androidAppContext.getSystemService(Context.AUDIO_SERVICE) as AudioManager Log.d(TAG, "setCallSound: set audio mode, speaker enabled: $speaker") @@ -271,59 +308,69 @@ private fun dropAudioManagerOverrides() { private fun ActiveCallOverlayLayout( call: Call, speakerCanBeEnabled: Boolean, + enabled: Boolean = true, dismiss: () -> Unit, toggleAudio: () -> Unit, toggleVideo: () -> Unit, toggleSound: () -> Unit, flipCamera: () -> Unit ) { - Column(Modifier.padding(DEFAULT_PADDING)) { - when (call.peerMedia ?: call.localMedia) { - CallMediaType.Video -> { - CallInfoView(call, alignment = Alignment.Start) - Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) { - DisabledBackgroundCallsButton() - } - Row(Modifier.fillMaxWidth().padding(horizontal = 6.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { - ToggleAudioButton(call, toggleAudio) - Spacer(Modifier.size(40.dp)) - IconButton(onClick = dismiss) { - Icon(painterResource(MR.images.ic_call_end_filled), stringResource(MR.strings.icon_descr_hang_up), tint = Color.Red, modifier = Modifier.size(64.dp)) - } - if (call.videoEnabled) { - ControlButton(call, painterResource(MR.images.ic_flip_camera_android_filled), MR.strings.icon_descr_flip_camera, flipCamera) - ControlButton(call, painterResource(MR.images.ic_videocam_filled), MR.strings.icon_descr_video_off, toggleVideo) - } else { - Spacer(Modifier.size(48.dp)) - ControlButton(call, painterResource(MR.images.ic_videocam_off), MR.strings.icon_descr_video_on, toggleVideo) - } - } + Column { + val media = call.peerMedia ?: call.localMedia + CloseSheetBar({ chatModel.activeCallViewIsCollapsed.value = true }, true, tintColor = Color(0xFFFFFFD8)) { + if (media == CallMediaType.Video) { + Text(call.contact.chatViewName, Modifier.fillMaxWidth().padding(end = DEFAULT_PADDING), color = Color(0xFFFFFFD8), style = MaterialTheme.typography.h2, overflow = TextOverflow.Ellipsis, maxLines = 1) } - CallMediaType.Audio -> { - Spacer(Modifier.fillMaxHeight().weight(1f)) - Column( - Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally, - verticalArrangement = Arrangement.Center - ) { - ProfileImage(size = 192.dp, image = call.contact.profile.image) - CallInfoView(call, alignment = Alignment.CenterHorizontally) - } - Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) { - DisabledBackgroundCallsButton() - } - Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_BOTTOM_PADDING), contentAlignment = Alignment.CenterStart) { - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { - IconButton(onClick = dismiss) { - Icon(painterResource(MR.images.ic_call_end_filled), stringResource(MR.strings.icon_descr_hang_up), tint = Color.Red, modifier = Modifier.size(64.dp)) + } + Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { + when (media) { + CallMediaType.Video -> { + VideoCallInfoView(call) + Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) { + DisabledBackgroundCallsButton() + } + Row(Modifier.fillMaxWidth().padding(horizontal = 6.dp), horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically) { + ToggleAudioButton(call, enabled, toggleAudio) + Spacer(Modifier.size(40.dp)) + IconButton(onClick = dismiss, enabled = enabled) { + Icon(painterResource(MR.images.ic_call_end_filled), stringResource(MR.strings.icon_descr_hang_up), tint = if (enabled) Color.Red else MaterialTheme.colors.secondary, modifier = Modifier.size(64.dp)) + } + if (call.videoEnabled) { + ControlButton(call, painterResource(MR.images.ic_flip_camera_android_filled), MR.strings.icon_descr_flip_camera, enabled, flipCamera) + ControlButton(call, painterResource(MR.images.ic_videocam_filled), MR.strings.icon_descr_video_off, enabled, toggleVideo) + } else { + Spacer(Modifier.size(48.dp)) + ControlButton(call, painterResource(MR.images.ic_videocam_off), MR.strings.icon_descr_video_on, enabled, toggleVideo) } } - Box(Modifier.padding(start = 32.dp)) { - ToggleAudioButton(call, toggleAudio) + } + + CallMediaType.Audio -> { + Spacer(Modifier.fillMaxHeight().weight(1f)) + Column( + Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally, + verticalArrangement = Arrangement.Center + ) { + ProfileImage(size = 192.dp, image = call.contact.profile.image) + AudioCallInfoView(call) } - Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) { - Box(Modifier.padding(end = 32.dp)) { - ToggleSoundButton(call, speakerCanBeEnabled, toggleSound) + Box(Modifier.fillMaxWidth().fillMaxHeight().weight(1f), contentAlignment = Alignment.BottomCenter) { + DisabledBackgroundCallsButton() + } + Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_BOTTOM_PADDING), contentAlignment = Alignment.CenterStart) { + Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.Center) { + IconButton(onClick = dismiss, enabled = enabled) { + Icon(painterResource(MR.images.ic_call_end_filled), stringResource(MR.strings.icon_descr_hang_up), tint = if (enabled) Color.Red else MaterialTheme.colors.secondary, modifier = Modifier.size(64.dp)) + } + } + Box(Modifier.padding(start = 32.dp)) { + ToggleAudioButton(call, enabled, toggleAudio) + } + Box(Modifier.fillMaxWidth(), contentAlignment = Alignment.CenterEnd) { + Box(Modifier.padding(end = 32.dp)) { + ToggleSoundButton(call, speakerCanBeEnabled && enabled, toggleSound) + } } } } @@ -333,7 +380,7 @@ private fun ActiveCallOverlayLayout( } @Composable -private fun ControlButton(call: Call, icon: Painter, iconText: StringResource, action: () -> Unit, enabled: Boolean = true) { +private fun ControlButton(call: Call, icon: Painter, iconText: StringResource, enabled: Boolean = true, action: () -> Unit) { if (call.hasMedia) { IconButton(onClick = action, enabled = enabled) { Icon(icon, stringResource(iconText), tint = if (enabled) Color(0xFFFFFFD8) else MaterialTheme.colors.secondary, modifier = Modifier.size(40.dp)) @@ -344,28 +391,26 @@ private fun ControlButton(call: Call, icon: Painter, iconText: StringResource, a } @Composable -private fun ToggleAudioButton(call: Call, toggleAudio: () -> Unit) { +private fun ToggleAudioButton(call: Call, enabled: Boolean = true, toggleAudio: () -> Unit) { if (call.audioEnabled) { - ControlButton(call, painterResource(MR.images.ic_mic), MR.strings.icon_descr_audio_off, toggleAudio) + ControlButton(call, painterResource(MR.images.ic_mic), MR.strings.icon_descr_audio_off, enabled, toggleAudio) } else { - ControlButton(call, painterResource(MR.images.ic_mic_off), MR.strings.icon_descr_audio_on, toggleAudio) + ControlButton(call, painterResource(MR.images.ic_mic_off), MR.strings.icon_descr_audio_on, enabled, toggleAudio) } } @Composable private fun ToggleSoundButton(call: Call, enabled: Boolean, toggleSound: () -> Unit) { if (call.soundSpeaker) { - ControlButton(call, painterResource(MR.images.ic_volume_up), MR.strings.icon_descr_speaker_off, toggleSound, enabled) + ControlButton(call, painterResource(MR.images.ic_volume_up), MR.strings.icon_descr_speaker_off, enabled, toggleSound) } else { - ControlButton(call, painterResource(MR.images.ic_volume_down), MR.strings.icon_descr_speaker_on, toggleSound, enabled) + ControlButton(call, painterResource(MR.images.ic_volume_down), MR.strings.icon_descr_speaker_on, enabled, toggleSound) } } @Composable -fun CallInfoView(call: Call, alignment: Alignment.Horizontal) { - @Composable fun InfoText(text: String, style: TextStyle = MaterialTheme.typography.body2) = - Text(text, color = Color(0xFFFFFFD8), style = style) - Column(horizontalAlignment = alignment) { +fun AudioCallInfoView(call: Call) { + Column(horizontalAlignment = Alignment.CenterHorizontally) { InfoText(call.contact.chatViewName, style = MaterialTheme.typography.h2) InfoText(call.callState.text) @@ -375,6 +420,21 @@ fun CallInfoView(call: Call, alignment: Alignment.Horizontal) { } } +@Composable +fun VideoCallInfoView(call: Call) { + Column(horizontalAlignment = Alignment.Start) { + InfoText(call.callState.text) + + val connInfo = call.connectionInfo + val connInfoText = if (connInfo == null) "" else " (${connInfo.text})" + InfoText(call.encryptionStatus + connInfoText) + } +} + +@Composable +fun InfoText(text: String, modifier: Modifier = Modifier, style: TextStyle = MaterialTheme.typography.body2) = + Text(text, modifier, color = Color(0xFFFFFFD8), style = style) + @Composable private fun DisabledBackgroundCallsButton() { var show by remember { mutableStateOf(!platform.androidIsBackgroundCallAllowed()) } @@ -452,7 +512,6 @@ private fun DisabledBackgroundCallsButton() { @Composable fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIMessage) -> Unit) { - val scope = rememberCoroutineScope() val webView = remember { mutableStateOf<WebView?>(null) } val permissionsState = rememberMultiplePermissionsState( permissions = listOf( @@ -475,10 +534,10 @@ fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIM } lifecycleOwner.lifecycle.addObserver(observer) onDispose { - val wv = webView.value - if (wv != null) processCommand(wv, WCallCommand.End) lifecycleOwner.lifecycle.removeObserver(observer) - webView.value?.destroy() +// val wv = webView.value +// if (wv != null) processCommand(wv, WCallCommand.End) +// webView.value?.destroy() webView.value = null } } @@ -505,7 +564,7 @@ fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIM Box(Modifier.fillMaxSize()) { AndroidView( factory = { AndroidViewContext -> - WebView(AndroidViewContext).apply { + (staticWebView ?: WebView(androidAppContext)).apply { layoutParams = ViewGroup.LayoutParams( ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT, @@ -530,7 +589,11 @@ fun WebRTCView(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIM webViewSettings.javaScriptEnabled = true webViewSettings.mediaPlaybackRequiresUserGesture = false webViewSettings.cacheMode = WebSettings.LOAD_NO_CACHE - this.loadUrl("file:android_asset/www/android/call.html") + if (staticWebView == null) { + this.loadUrl("file:android_asset/www/android/call.html") + } else { + webView.value = this + } } } ) { /* WebView */ } @@ -554,6 +617,15 @@ class WebRTCInterface(private val onResponse: (WVAPIMessage) -> Unit) { } } +private fun updateActiveCall(initial: Call, transform: (Call) -> Call) { + val activeCall = chatModel.activeCall.value + if (activeCall != null && activeCall.contact.apiId == initial.contact.apiId) { + chatModel.activeCall.value = transform(activeCall) + } else { + Log.d(TAG, "withActiveCall: ignoring, not in call with the contact ${activeCall?.contact?.id}") + } +} + private class LocalContentWebViewClient(val webView: MutableState<WebView?>, private val assetLoader: WebViewAssetLoader) : WebViewClientCompat() { override fun shouldInterceptRequest( view: WebView, @@ -566,6 +638,7 @@ private class LocalContentWebViewClient(val webView: MutableState<WebView?>, pri super.onPageFinished(view, url) view.evaluateJavascript("sendMessageToNative = (msg) => WebRTCInterface.postMessage(JSON.stringify(msg))", null) webView.value = view + staticWebView = view Log.d(TAG, "WebRTCView: webview ready") // for debugging // view.evaluateJavascript("sendMessageToNative = ({resp}) => WebRTCInterface.postMessage(JSON.stringify({command: resp}))", null) @@ -579,6 +652,7 @@ fun PreviewActiveCallOverlayVideo() { ActiveCallOverlayLayout( call = Call( remoteHostId = null, + userProfile = Profile.sampleData, contact = Contact.sampleData, callState = CallState.Negotiated, localMedia = CallMediaType.Video, @@ -605,6 +679,7 @@ fun PreviewActiveCallOverlayAudio() { ActiveCallOverlayLayout( call = Call( remoteHostId = null, + userProfile = Profile.sampleData, contact = Contact.sampleData, callState = CallState.Negotiated, localMedia = CallMediaType.Audio, diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt index cb74664a48..4a8b912cdd 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.android.kt @@ -1,8 +1,112 @@ package chat.simplex.common.views.chatlist +import android.app.Activity +import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.MutableInteractionSource +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.material.* +import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.toArgb +import androidx.compose.ui.platform.* +import androidx.compose.ui.text.style.TextOverflow +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import chat.simplex.common.ANDROID_CALL_TOP_PADDING +import chat.simplex.common.model.durationText +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.call.* import chat.simplex.common.views.helpers.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.datetime.Clock + +private val CALL_INTERACTIVE_AREA_HEIGHT = 74.dp +private val CALL_TOP_OFFSET = (-10).dp +private val CALL_TOP_GREEN_LINE_HEIGHT = ANDROID_CALL_TOP_PADDING - CALL_TOP_OFFSET +private val CALL_BOTTOM_ICON_OFFSET = (-15).dp +private val CALL_BOTTOM_ICON_HEIGHT = CALL_INTERACTIVE_AREA_HEIGHT + CALL_BOTTOM_ICON_OFFSET @Composable -actual fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow<AnimatedViewState>) {} +actual fun ActiveCallInteractiveArea(call: Call, newChatSheetState: MutableStateFlow<AnimatedViewState>) { + val onClick = { platform.androidStartCallActivity(false) } + Box(Modifier.offset(y = CALL_TOP_OFFSET).height(CALL_INTERACTIVE_AREA_HEIGHT)) { + val source = remember { MutableInteractionSource() } + val indication = rememberRipple(bounded = true, 3000.dp) + Box(Modifier.height(CALL_TOP_GREEN_LINE_HEIGHT).clickable(onClick = onClick, indication = indication, interactionSource = source)) { + GreenLine(call) + } + Box( + Modifier + .offset(y = CALL_BOTTOM_ICON_OFFSET) + .size(CALL_BOTTOM_ICON_HEIGHT) + .background(SimplexGreen, CircleShape) + .clip(CircleShape) + .clickable(onClick = onClick, indication = indication, interactionSource = source) + .align(Alignment.BottomCenter), + contentAlignment = Alignment.Center + ) { + val media = call.peerMedia ?: call.localMedia + if (media == CallMediaType.Video) { + Icon(painterResource(MR.images.ic_videocam_filled), null, Modifier.size(27.dp).offset(x = 2.5.dp, y = 2.dp), tint = Color.White) + } else { + Icon(painterResource(MR.images.ic_call_filled), null, Modifier.size(27.dp).offset(x = -0.5.dp, y = 2.dp), tint = Color.White) + } + } + } +} + +@Composable +private fun GreenLine(call: Call) { + Row( + Modifier + .fillMaxSize() + .background(SimplexGreen) + .padding(top = -CALL_TOP_OFFSET) + .padding(horizontal = DEFAULT_PADDING), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.Center + ) { + ContactName(call.contact.displayName) + Spacer(Modifier.weight(1f)) + CallDuration(call) + } + val window = (LocalContext.current as Activity).window + DisposableEffect(Unit) { + window.statusBarColor = SimplexGreen.toArgb() + onDispose { + window.statusBarColor = Color.Black.toArgb() + } + } +} + +@Composable +private fun ContactName(name: String) { + Text(name, Modifier.width(windowWidth() * 0.35f), color = Color.White, maxLines = 1, overflow = TextOverflow.Ellipsis) +} + +@Composable +private fun CallDuration(call: Call) { + val connectedAt = call.connectedAt + if (connectedAt != null) { + val time = remember { mutableStateOf(durationText(0)) } + LaunchedEffect(Unit) { + while (true) { + time.value = durationText((Clock.System.now() - connectedAt).inWholeSeconds.toInt()) + delay(250) + } + } + val text = time.value + val sp40Or50 = with(LocalDensity.current) { if (text.length >= 6) 60.sp.toDp() else 42.sp.toDp() } + val offset = with(LocalDensity.current) { 7.sp.toDp() } + Text(text, Modifier.offset(x = offset).widthIn(min = sp40Or50), color = Color.White) + } +} diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.android.kt index df2499926f..83677f3318 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.android.kt @@ -2,6 +2,7 @@ package chat.simplex.common.views.database import SectionItemView import SectionTextFooter +import TextIconSpaced import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.Composable @@ -22,8 +23,9 @@ actual fun SavePassphraseSetting( useKeychain: Boolean, initialRandomDBPassphrase: Boolean, storedKey: Boolean, - progressIndicator: Boolean, minHeight: Dp, + enabled: Boolean, + smallPadding: Boolean, onCheckedChange: (Boolean) -> Unit, ) { SectionItemView(minHeight = minHeight) { @@ -33,7 +35,11 @@ actual fun SavePassphraseSetting( stringResource(MR.strings.save_passphrase_in_keychain), tint = if (storedKey) SimplexGreen else MaterialTheme.colors.secondary ) - Spacer(Modifier.padding(horizontal = 4.dp)) + if (smallPadding) { + Spacer(Modifier.padding(horizontal = 4.dp)) + } else { + TextIconSpaced(false) + } Text( stringResource(MR.strings.save_passphrase_in_keychain), Modifier.padding(end = 24.dp), @@ -43,7 +49,7 @@ actual fun SavePassphraseSetting( DefaultSwitch( checked = useKeychain, onCheckedChange = onCheckedChange, - enabled = !initialRandomDBPassphrase && !progressIndicator + enabled = enabled ) } } @@ -55,13 +61,14 @@ actual fun DatabaseEncryptionFooter( chatDbEncrypted: Boolean?, storedKey: MutableState<Boolean>, initialRandomDBPassphrase: MutableState<Boolean>, + migration: Boolean, ) { if (chatDbEncrypted == false) { SectionTextFooter(generalGetString(MR.strings.database_is_not_encrypted)) } else if (useKeychain.value) { if (storedKey.value) { SectionTextFooter(generalGetString(MR.strings.keychain_is_storing_securely)) - if (initialRandomDBPassphrase.value) { + if (initialRandomDBPassphrase.value && !migration) { SectionTextFooter(generalGetString(MR.strings.encrypted_with_random_passphrase)) } else { SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase)) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt index e02c011fac..ccb85c3982 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/views/usersettings/Appearance.android.kt @@ -85,8 +85,8 @@ fun AppearanceScope.AppearanceLayout( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), editColor: (ThemeColor, Color) -> Unit, ) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.appearance_settings)) SectionView(stringResource(MR.strings.settings_section_title_language), padding = PaddingValues()) { diff --git a/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c b/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c index d0581b4336..b9b5277aeb 100644 --- a/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c +++ b/apps/multiplatform/common/src/commonMain/cpp/android/simplex-api.c @@ -36,7 +36,7 @@ Java_chat_simplex_common_platform_CoreKt_initHS(__unused JNIEnv *env, __unused j char *argv[] = { "simplex", "+RTS", // requires `hs_init_with_rtsopts` - "-A16m", // chunk size for new allocations + "-A64m", // chunk size for new allocations "-H64m", // initial heap size "-xn", // non-moving GC NULL diff --git a/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c b/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c index 90504e25c1..5c921c400d 100644 --- a/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c +++ b/apps/multiplatform/common/src/commonMain/cpp/desktop/simplex-api.c @@ -10,10 +10,10 @@ JNIEXPORT void JNICALL Java_chat_simplex_common_platform_CoreKt_initHS(JNIEnv *env, jclass clazz) { #ifdef _WIN32 int argc = 4; - char *argv[] = {"simplex", "+RTS", "-A16m", "-H64m", NULL}; // non-moving GC is broken on windows with GHC 9.4-9.6.3 + char *argv[] = {"simplex", "+RTS", "-A64m", "-H64m", NULL}; // non-moving GC is broken on windows with GHC 9.4-9.6.3 #else int argc = 5; - char *argv[] = {"simplex", "+RTS", "-A16m", "-H64m", "-xn", NULL}; // see android/simplex-api.c for details + char *argv[] = {"simplex", "+RTS", "-A64m", "-H64m", "-xn", NULL}; // see android/simplex-api.c for details #endif char **pargv = argv; hs_init_with_rtsopts(&argc, &pargv); diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt index 57959af4c6..e7dda42ade 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/App.kt @@ -1,16 +1,19 @@ package chat.simplex.common import androidx.compose.animation.core.Animatable -import androidx.compose.foundation.background -import androidx.compose.foundation.clickable +import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.* +import androidx.compose.foundation.shape.CircleShape import androidx.compose.material.* +import androidx.compose.material.ripple.rememberRipple import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.dp import chat.simplex.common.views.usersettings.SetDeliveryReceiptsView @@ -20,8 +23,7 @@ import chat.simplex.common.ui.theme.* import chat.simplex.common.views.CreateFirstProfile import chat.simplex.common.views.helpers.SimpleButton import chat.simplex.common.views.SplashView -import chat.simplex.common.views.call.ActiveCallView -import chat.simplex.common.views.call.IncomingCallAlertView +import chat.simplex.common.views.call.* import chat.simplex.common.views.chat.ChatView import chat.simplex.common.views.chatlist.* import chat.simplex.common.views.database.DatabaseErrorView @@ -108,6 +110,13 @@ fun MainScreen() { val localUserCreated = chatModel.localUserCreated.value var showInitializationView by remember { mutableStateOf(false) } when { + onboarding == OnboardingStage.Step1_SimpleXInfo && chatModel.migrationState.value != null -> { + // In migration process. Nothing should interrupt it, that's why it's the first branch in when() + SimpleXInfo(chatModel, onboarding = true) + if (appPlatform.isDesktop) { + ModalManager.fullscreen.showInView() + } + } chatModel.dbMigrationInProgress.value -> DefaultProgressView(stringResource(MR.strings.database_migration_in_progress)) chatModel.chatDbStatus.value == null && showInitializationView -> DefaultProgressView(stringResource(MR.strings.opening_database)) showChatDatabaseError -> { @@ -169,7 +178,17 @@ fun MainScreen() { } } else { if (chatModel.showCallView.value) { - ActiveCallView() + if (appPlatform.isAndroid) { + LaunchedEffect(Unit) { + // This if prevents running the activity in the following condition: + // - the activity already started before and was destroyed by collapsing active call (start audio call, press back button, go to a launcher) + if (!chatModel.activeCallViewIsCollapsed.value) { + platform.androidStartCallActivity(false) + } + } + } else { + ActiveCallView() + } } else { // It's needed for privacy settings toggle, so it can be shown even if the app is passcode unlocked ModalManager.fullscreen.showPasscodeInView() @@ -206,9 +225,13 @@ fun MainScreen() { } } +val ANDROID_CALL_TOP_PADDING = 40.dp + @Composable fun AndroidScreen(settingsState: SettingsViewState) { BoxWithConstraints { + val call = remember { chatModel.activeCall} .value + val showCallArea = call != null && call.callState != CallState.WaitCapabilities && call.callState != CallState.InvitationAccepted var currentChatId by rememberSaveable { mutableStateOf(chatModel.chatId.value) } val offset = remember { Animatable(if (chatModel.chatId.value == null) 0f else maxWidth.value) } Box( @@ -216,6 +239,7 @@ fun AndroidScreen(settingsState: SettingsViewState) { .graphicsLayer { translationX = -offset.value.dp.toPx() } + .padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp) ) { StartPartOfScreen(settingsState) } @@ -242,11 +266,17 @@ fun AndroidScreen(settingsState: SettingsViewState) { } } } - Box(Modifier.graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() }) Box2@{ + Box(Modifier + .graphicsLayer { translationX = maxWidth.toPx() - offset.value.dp.toPx() } + .padding(top = if (showCallArea) ANDROID_CALL_TOP_PADDING else 0.dp) + ) Box2@{ currentChatId?.let { ChatView(it, chatModel, onComposed) } } + if (call != null && showCallArea) { + ActiveCallInteractiveArea(call, remember { MutableStateFlow(AnimatedViewState.GONE) }) + } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt index c19abdccc9..a8a5797d71 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/ChatModel.kt @@ -13,6 +13,8 @@ import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.* import chat.simplex.common.views.chat.ComposeState import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.migration.MigrationToDeviceState +import chat.simplex.common.views.migration.MigrationToState import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource @@ -96,6 +98,7 @@ object ChatModel { val activeCallInvitation = mutableStateOf<RcvCallInvitation?>(null) val activeCall = mutableStateOf<Call?>(null) val activeCallViewIsVisible = mutableStateOf<Boolean>(false) + val activeCallViewIsCollapsed = mutableStateOf<Boolean>(false) val callCommand = mutableStateListOf<WCallCommand>() val showCallView = mutableStateOf(false) val switchingCall = mutableStateOf(false) @@ -103,6 +106,8 @@ object ChatModel { // currently showing invitation val showingInvitation = mutableStateOf(null as ShowingInvitation?) + val migrationState: MutableState<MigrationToState?> by lazy { mutableStateOf(MigrationToDeviceState.makeMigrationState()) } + var draft = mutableStateOf(null as ComposeState?) var draftChatId = mutableStateOf(null as String?) @@ -1122,11 +1127,19 @@ data class Connection( val viaGroupLink: Boolean, val customUserProfileId: Long? = null, val connectionCode: SecurityCode? = null, + val pqSupport: Boolean, + val pqEncryption: Boolean, + val pqSndEnabled: Boolean? = null, + val pqRcvEnabled: Boolean? = null, val connectionStats: ConnectionStats? = null ) { val id: ChatId get() = ":$connId" + + val connPQEnabled: Boolean + get() = pqSndEnabled == true && pqRcvEnabled == true + companion object { - val sampleData = Connection(connId = 1, agentConnId = "abc", connStatus = ConnStatus.Ready, connLevel = 0, viaGroupLink = false, peerChatVRange = VersionRange(1, 1), customUserProfileId = null) + val sampleData = Connection(connId = 1, agentConnId = "abc", connStatus = ConnStatus.Ready, connLevel = 0, viaGroupLink = false, peerChatVRange = VersionRange(1, 1), customUserProfileId = null, pqSupport = false, pqEncryption = false) } } @@ -1821,7 +1834,7 @@ data class ChatItem ( is CIContent.SndGroupInvitation -> false is CIContent.RcvDirectEventContent -> when (content.rcvDirectEvent) { is RcvDirectEvent.ContactDeleted -> false - is RcvDirectEvent.ProfileUpdated -> true + is RcvDirectEvent.ProfileUpdated -> false } is CIContent.RcvGroupEventContent -> when (content.rcvGroupEvent) { is RcvGroupEvent.MemberAdded -> false @@ -1852,6 +1865,10 @@ data class ChatItem ( is CIContent.SndModerated -> false is CIContent.RcvModerated -> false is CIContent.RcvBlocked -> false + is CIContent.SndDirectE2EEInfo -> false + is CIContent.RcvDirectE2EEInfo -> false + is CIContent.SndGroupE2EEInfo -> false + is CIContent.RcvGroupE2EEInfo -> false is CIContent.InvalidJSON -> false } @@ -2282,6 +2299,10 @@ sealed class CIContent: ItemContent { @Serializable @SerialName("sndModerated") object SndModerated: CIContent() { override val msgContent: MsgContent? get() = null } @Serializable @SerialName("rcvModerated") object RcvModerated: CIContent() { override val msgContent: MsgContent? get() = null } @Serializable @SerialName("rcvBlocked") object RcvBlocked: CIContent() { override val msgContent: MsgContent? get() = null } + @Serializable @SerialName("sndDirectE2EEInfo") class SndDirectE2EEInfo(val e2eeInfo: E2EEInfo): CIContent() { override val msgContent: MsgContent? get() = null } + @Serializable @SerialName("rcvDirectE2EEInfo") class RcvDirectE2EEInfo(val e2eeInfo: E2EEInfo): CIContent() { override val msgContent: MsgContent? get() = null } + @Serializable @SerialName("sndGroupE2EEInfo") class SndGroupE2EEInfo(val e2eeInfo: E2EEInfo): CIContent() { override val msgContent: MsgContent? get() = null } + @Serializable @SerialName("rcvGroupE2EEInfo") class RcvGroupE2EEInfo(val e2eeInfo: E2EEInfo): CIContent() { override val msgContent: MsgContent? get() = null } @Serializable @SerialName("invalidJSON") data class InvalidJSON(val json: String): CIContent() { override val msgContent: MsgContent? get() = null } override val text: String get() = when (this) { @@ -2311,6 +2332,10 @@ sealed class CIContent: ItemContent { is SndModerated -> generalGetString(MR.strings.moderated_description) is RcvModerated -> generalGetString(MR.strings.moderated_description) is RcvBlocked -> generalGetString(MR.strings.blocked_by_admin_item_description) + is SndDirectE2EEInfo -> directE2EEInfoStr(e2eeInfo) + is RcvDirectE2EEInfo -> directE2EEInfoStr(e2eeInfo) + is SndGroupE2EEInfo -> e2eeInfoNoPQStr + is RcvGroupE2EEInfo -> e2eeInfoNoPQStr is InvalidJSON -> "invalid data" } @@ -2329,6 +2354,15 @@ sealed class CIContent: ItemContent { } companion object { + fun directE2EEInfoStr(e2EEInfo: E2EEInfo): String = + if (e2EEInfo.pqEnabled) { + generalGetString(MR.strings.e2ee_info_pq_short) + } else { + e2eeInfoNoPQStr + } + + private val e2eeInfoNoPQStr: String = generalGetString(MR.strings.e2ee_info_no_pq_short) + fun featureText(feature: Feature, enabled: String, param: Int?): String = if (feature.hasParam) { "${feature.text}: ${timeText(param)}" @@ -2743,6 +2777,9 @@ enum class CIGroupInvitationStatus { @SerialName("expired") Expired; } +@Serializable +class E2EEInfo (val pqEnabled: Boolean) {} + object MsgContentSerializer : KSerializer<MsgContent> { override val descriptor: SerialDescriptor = buildSerialDescriptor("MsgContent", PolymorphicKind.SEALED) { element("MCText", buildClassSerialDescriptor("MCText") { @@ -2940,10 +2977,17 @@ enum class FormatColor(val color: String) { class SndFileTransfer() {} @Serializable -class RcvFileTransfer() {} +data class RcvFileTransfer( + val fileId: Long, +) @Serializable -class FileTransferMeta() {} +data class FileTransferMeta( + val fileId: Long, + val fileName: String, + val filePath: String, + val fileSize: Long, +) @Serializable enum class CICallStatus { @@ -3096,6 +3140,7 @@ sealed class RcvConnEvent { @Serializable @SerialName("switchQueue") class SwitchQueue(val phase: SwitchPhase): RcvConnEvent() @Serializable @SerialName("ratchetSync") class RatchetSync(val syncStatus: RatchetSyncState): RcvConnEvent() @Serializable @SerialName("verificationCodeReset") object VerificationCodeReset: RcvConnEvent() + @Serializable @SerialName("pqEnabled") class PQEnabled(val enabled: Boolean): RcvConnEvent() val text: String get() = when (this) { is SwitchQueue -> when (phase) { @@ -3104,6 +3149,11 @@ sealed class RcvConnEvent { } is RatchetSync -> ratchetSyncStatusToText(syncStatus) is VerificationCodeReset -> generalGetString(MR.strings.rcv_conn_event_verification_code_reset) + is PQEnabled -> if (enabled) { + generalGetString(MR.strings.conn_event_enabled_pq) + } else { + generalGetString(MR.strings.conn_event_disabled_pq) + } } } @@ -3121,6 +3171,7 @@ fun ratchetSyncStatusToText(ratchetSyncStatus: RatchetSyncState): String { sealed class SndConnEvent { @Serializable @SerialName("switchQueue") class SwitchQueue(val phase: SwitchPhase, val member: GroupMemberRef? = null): SndConnEvent() @Serializable @SerialName("ratchetSync") class RatchetSync(val syncStatus: RatchetSyncState, val member: GroupMemberRef? = null): SndConnEvent() + @Serializable @SerialName("pqEnabled") class PQEnabled(val enabled: Boolean): SndConnEvent() val text: String get() = when (this) { @@ -3149,6 +3200,12 @@ sealed class SndConnEvent { } ratchetSyncStatusToText(syncStatus) } + + is PQEnabled -> if (enabled) { + generalGetString(MR.strings.conn_event_enabled_pq) + } else { + generalGetString(MR.strings.conn_event_disabled_pq) + } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt index d6cea330b1..0d6420dad2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -4,12 +4,15 @@ import chat.simplex.common.views.helpers.* import androidx.compose.runtime.* import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.painter.Painter +import chat.simplex.common.model.ChatController.getNetCfg +import chat.simplex.common.model.ChatController.setNetCfg import chat.simplex.common.model.ChatModel.updatingChatsMutex import chat.simplex.common.model.ChatModel.changingActiveUserMutex import dev.icerock.moko.resources.compose.painterResource import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.* +import chat.simplex.common.views.migration.MigrationFileLinkData import chat.simplex.common.views.onboarding.OnboardingStage import chat.simplex.common.views.usersettings.* import com.charleskorn.kaml.Yaml @@ -72,7 +75,7 @@ class AppPreferences { val value = _callOnLockScreen.get() ?: return CallOnLockScreen.default return try { CallOnLockScreen.valueOf(value) - } catch (e: Error) { + } catch (e: Throwable) { CallOnLockScreen.default } }, @@ -92,7 +95,7 @@ class AppPreferences { val value = _simplexLinkMode.get() ?: return SimplexLinkMode.default return try { SimplexLinkMode.valueOf(value) - } catch (e: Error) { + } catch (e: Throwable) { SimplexLinkMode.default } }, @@ -120,7 +123,7 @@ class AppPreferences { val value = _networkSessionMode.get() ?: return TransportSessionMode.default return try { TransportSessionMode.valueOf(value) - } catch (e: Error) { + } catch (e: Throwable) { TransportSessionMode.default } }, @@ -144,6 +147,8 @@ class AppPreferences { val appLanguage = mkStrPreference(SHARED_PREFS_APP_LANGUAGE, null) val onboardingStage = mkEnumPreference(SHARED_PREFS_ONBOARDING_STAGE, OnboardingStage.OnboardingComplete) { OnboardingStage.values().firstOrNull { it.name == this } } + val migrationToStage = mkStrPreference(SHARED_PREFS_MIGRATION_TO_STAGE, null) + val migrationFromStage = mkStrPreference(SHARED_PREFS_MIGRATION_FROM_STAGE, null) val storeDBPassphrase = mkBoolPreference(SHARED_PREFS_STORE_DB_PASSPHRASE, true) val initialRandomDBPassphrase = mkBoolPreference(SHARED_PREFS_INITIAL_RANDOM_DB_PASSPHRASE, false) val encryptedDBPassphrase = mkStrPreference(SHARED_PREFS_ENCRYPTED_DB_PASSPHRASE, null) @@ -156,6 +161,7 @@ class AppPreferences { val confirmDBUpgrades = mkBoolPreference(SHARED_PREFS_CONFIRM_DB_UPGRADES, false) val selfDestruct = mkBoolPreference(SHARED_PREFS_SELF_DESTRUCT, false) val selfDestructDisplayName = mkStrPreference(SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME, null) + val pqExperimentalEnabled = mkBoolPreference(SHARED_PREFS_PQ_EXPERIMENTAL_ENABLED, false) val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM.name) val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.name) @@ -176,6 +182,11 @@ class AppPreferences { val offerRemoteMulticast = mkBoolPreference(SHARED_PREFS_OFFER_REMOTE_MULTICAST, true) val desktopWindowState = mkStrPreference(SHARED_PREFS_DESKTOP_WINDOW_STATE, null) + + + val iosCallKitEnabled = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_ENABLED, true) + val iosCallKitCallsInRecents = mkBoolPreference(SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS, false) + private fun mkIntPreference(prefName: String, default: Int) = SharedPreference( @@ -276,6 +287,8 @@ class AppPreferences { private const val SHARED_PREFS_CHAT_ARCHIVE_TIME = "ChatArchiveTime" private const val SHARED_PREFS_APP_LANGUAGE = "AppLanguage" private const val SHARED_PREFS_ONBOARDING_STAGE = "OnboardingStage" + const val SHARED_PREFS_MIGRATION_TO_STAGE = "MigrationToStage" + const val SHARED_PREFS_MIGRATION_FROM_STAGE = "MigrationFromStage" private const val SHARED_PREFS_CHAT_LAST_START = "ChatLastStart" private const val SHARED_PREFS_CHAT_STOPPED = "ChatStopped" private const val SHARED_PREFS_DEVELOPER_TOOLS = "DeveloperTools" @@ -312,6 +325,7 @@ class AppPreferences { private const val SHARED_PREFS_CONFIRM_DB_UPGRADES = "ConfirmDBUpgrades" private const val SHARED_PREFS_SELF_DESTRUCT = "LocalAuthenticationSelfDestruct" private const val SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME = "LocalAuthenticationSelfDestructDisplayName" + private const val SHARED_PREFS_PQ_EXPERIMENTAL_ENABLED = "PQExperimentalEnabled" private const val SHARED_PREFS_CURRENT_THEME = "CurrentTheme" private const val SHARED_PREFS_SYSTEM_DARK_THEME = "SystemDarkTheme" private const val SHARED_PREFS_THEMES = "Themes" @@ -324,6 +338,9 @@ class AppPreferences { private const val SHARED_PREFS_CONNECT_REMOTE_VIA_MULTICAST_AUTO = "ConnectRemoteViaMulticastAuto" private const val SHARED_PREFS_OFFER_REMOTE_MULTICAST = "OfferRemoteMulticast" private const val SHARED_PREFS_DESKTOP_WINDOW_STATE = "DesktopWindowState" + + private const val SHARED_PREFS_IOS_CALL_KIT_ENABLED = "iOSCallKitEnabled" + private const val SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS = "iOSCallKitCallsInRecents" } } @@ -376,7 +393,7 @@ object ChatController { } Log.d(TAG, "startChat: running") } - } catch (e: Error) { + } catch (e: Throwable) { Log.e(TAG, "failed starting chat $e") throw e } @@ -394,12 +411,22 @@ object ChatController { startReceiver() setLocalDeviceName(appPrefs.deviceNameForRemoteAccess.get()!!) Log.d(TAG, "startChat: started without user") - } catch (e: Error) { + } catch (e: Throwable) { Log.e(TAG, "failed starting chat without user $e") throw e } } + suspend fun startChatWithTemporaryDatabase(ctrl: ChatCtrl, netCfg: NetCfg): User? { + Log.d(TAG, "startChatWithTemporaryDatabase") + val migrationActiveUser = apiGetActiveUser(null, ctrl) ?: apiCreateActiveUser(null, Profile(displayName = "Temp", fullName = ""), ctrl = ctrl) + apiSetNetworkConfig(netCfg, ctrl) + apiSetTempFolder(getMigrationTempFilesDirectory().absolutePath, ctrl) + apiSetFilesFolder(getMigrationTempFilesDirectory().absolutePath, ctrl) + apiStartChat(ctrl) + return migrationActiveUser + } + suspend fun changeActiveUser(rhId: Long?, toUserId: Long, viewPwd: String?) { try { changeActiveUser_(rhId, toUserId, viewPwd) @@ -476,8 +503,8 @@ object ChatController { } } - suspend fun sendCmd(rhId: Long?, cmd: CC): CR { - val ctrl = ctrl ?: throw Exception("Controller is not initialized") + suspend fun sendCmd(rhId: Long?, cmd: CC, otherCtrl: ChatCtrl? = null): CR { + val ctrl = otherCtrl ?: ctrl ?: throw Exception("Controller is not initialized") return withContext(Dispatchers.IO) { val c = cmd.cmdString @@ -494,7 +521,7 @@ object ChatController { } } - private fun recvMsg(ctrl: ChatCtrl): APIResponse? { + fun recvMsg(ctrl: ChatCtrl): APIResponse? { val json = chatRecvMsgWait(ctrl, MESSAGE_TIMEOUT) return if (json == "") { null @@ -507,8 +534,8 @@ object ChatController { } } - suspend fun apiGetActiveUser(rh: Long?): User? { - val r = sendCmd(rh, CC.ShowActiveUser()) + suspend fun apiGetActiveUser(rh: Long?, ctrl: ChatCtrl? = null): User? { + val r = sendCmd(rh, CC.ShowActiveUser(), ctrl) if (r is CR.ActiveUser) return r.user.updateRemoteHostId(rh) Log.d(TAG, "apiGetActiveUser: ${r.responseType} ${r.details}") if (rh == null) { @@ -517,8 +544,8 @@ object ChatController { return null } - suspend fun apiCreateActiveUser(rh: Long?, p: Profile?, sameServers: Boolean = false, pastTimestamp: Boolean = false): User? { - val r = sendCmd(rh, CC.CreateActiveUser(p, sameServers = sameServers, pastTimestamp = pastTimestamp)) + suspend fun apiCreateActiveUser(rh: Long?, p: Profile?, sameServers: Boolean = false, pastTimestamp: Boolean = false, ctrl: ChatCtrl? = null): User? { + val r = sendCmd(rh, CC.CreateActiveUser(p, sameServers = sameServers, pastTimestamp = pastTimestamp), ctrl) if (r is CR.ActiveUser) return r.user.updateRemoteHostId(rh) else if ( r is CR.ChatCmdError && r.chatError is ChatError.ChatErrorStore && r.chatError.storeError is StoreError.DuplicateName || @@ -596,12 +623,12 @@ object ChatController { throw Exception("failed to delete the user ${r.responseType} ${r.details}") } - suspend fun apiStartChat(): Boolean { - val r = sendCmd(null, CC.StartChat(mainApp = true)) + suspend fun apiStartChat(ctrl: ChatCtrl? = null): Boolean { + val r = sendCmd(null, CC.StartChat(mainApp = true), ctrl) when (r) { is CR.ChatStarted -> return true is CR.ChatRunning -> return false - else -> throw Error("failed starting chat: ${r.responseType} ${r.details}") + else -> throw Exception("failed starting chat: ${r.responseType} ${r.details}") } } @@ -609,46 +636,67 @@ object ChatController { val r = sendCmd(null, CC.ApiStopChat()) when (r) { is CR.ChatStopped -> return true - else -> throw Error("failed stopping chat: ${r.responseType} ${r.details}") + else -> throw Exception("failed stopping chat: ${r.responseType} ${r.details}") } } - suspend fun apiSetTempFolder(tempFolder: String) { - val r = sendCmd(null, CC.SetTempFolder(tempFolder)) + suspend fun apiSetTempFolder(tempFolder: String, ctrl: ChatCtrl? = null) { + val r = sendCmd(null, CC.SetTempFolder(tempFolder), ctrl) if (r is CR.CmdOk) return - throw Error("failed to set temp folder: ${r.responseType} ${r.details}") + throw Exception("failed to set temp folder: ${r.responseType} ${r.details}") } - suspend fun apiSetFilesFolder(filesFolder: String) { - val r = sendCmd(null, CC.SetFilesFolder(filesFolder)) + suspend fun apiSetFilesFolder(filesFolder: String, ctrl: ChatCtrl? = null) { + val r = sendCmd(null, CC.SetFilesFolder(filesFolder), ctrl) if (r is CR.CmdOk) return - throw Error("failed to set files folder: ${r.responseType} ${r.details}") + throw Exception("failed to set files folder: ${r.responseType} ${r.details}") } suspend fun apiSetRemoteHostsFolder(remoteHostsFolder: String) { val r = sendCmd(null, CC.SetRemoteHostsFolder(remoteHostsFolder)) if (r is CR.CmdOk) return - throw Error("failed to set remote hosts folder: ${r.responseType} ${r.details}") + throw Exception("failed to set remote hosts folder: ${r.responseType} ${r.details}") } suspend fun apiSetEncryptLocalFiles(enable: Boolean) = sendCommandOkResp(null, CC.ApiSetEncryptLocalFiles(enable)) + suspend fun apiSaveAppSettings(settings: AppSettings) { + val r = sendCmd(null, CC.ApiSaveSettings(settings)) + if (r is CR.CmdOk) return + throw Exception("failed to set app settings: ${r.responseType} ${r.details}") + } + + suspend fun apiGetAppSettings(settings: AppSettings): AppSettings { + val r = sendCmd(null, CC.ApiGetSettings(settings)) + if (r is CR.AppSettingsR) return r.appSettings + throw Exception("failed to get app settings: ${r.responseType} ${r.details}") + } + + suspend fun apiSetPQEncryption(enable: Boolean) = sendCommandOkResp(null, CC.ApiSetPQEncryption(enable)) + + suspend fun apiSetContactPQ(rh: Long?, contactId: Long, enable: Boolean): Contact? { + val r = sendCmd(rh, CC.ApiSetContactPQ(contactId, enable)) + if (r is CR.ContactPQAllowed) return r.contact + apiErrorAlert("apiSetContactPQ", "Error allowing contact PQ", r) + return null + } + suspend fun apiExportArchive(config: ArchiveConfig) { val r = sendCmd(null, CC.ApiExportArchive(config)) if (r is CR.CmdOk) return - throw Error("failed to export archive: ${r.responseType} ${r.details}") + throw Exception("failed to export archive: ${r.responseType} ${r.details}") } suspend fun apiImportArchive(config: ArchiveConfig): List<ArchiveError> { val r = sendCmd(null, CC.ApiImportArchive(config)) if (r is CR.ArchiveImported) return r.archiveErrors - throw Error("failed to import archive: ${r.responseType} ${r.details}") + throw Exception("failed to import archive: ${r.responseType} ${r.details}") } suspend fun apiDeleteStorage() { val r = sendCmd(null, CC.ApiDeleteStorage()) if (r is CR.CmdOk) return - throw Error("failed to delete storage: ${r.responseType} ${r.details}") + throw Exception("failed to delete storage: ${r.responseType} ${r.details}") } suspend fun apiStorageEncryption(currentKey: String = "", newKey: String = ""): CR.ChatCmdError? { @@ -658,6 +706,13 @@ object ChatController { throw Exception("failed to set storage encryption: ${r.responseType} ${r.details}") } + suspend fun testStorageEncryption(key: String, ctrl: ChatCtrl? = null): CR.ChatCmdError? { + val r = sendCmd(null, CC.TestStorageEncryption(key), ctrl) + if (r is CR.CmdOk) return null + else if (r is CR.ChatCmdError) return r + throw Exception("failed to test storage encryption: ${r.responseType} ${r.details}") + } + suspend fun apiGetChats(rh: Long?): List<Chat> { val userId = kotlin.runCatching { currentUserId("apiGetChats") }.getOrElse { return emptyList() } val r = sendCmd(rh, CC.ApiGetChats(userId)) @@ -794,8 +849,8 @@ object ChatController { throw Exception("failed to set chat item TTL: ${r.responseType} ${r.details}") } - suspend fun apiSetNetworkConfig(cfg: NetCfg): Boolean { - val r = sendCmd(null, CC.APISetNetworkConfig(cfg)) + suspend fun apiSetNetworkConfig(cfg: NetCfg, ctrl: ChatCtrl? = null): Boolean { + val r = sendCmd(null, CC.APISetNetworkConfig(cfg), ctrl) return when (r) { is CR.CmdOk -> true else -> { @@ -1225,6 +1280,36 @@ object ChatController { return false } + suspend fun uploadStandaloneFile(user: UserLike, file: CryptoFile, ctrl: ChatCtrl? = null): Pair<FileTransferMeta?, String?> { + val r = sendCmd(null, CC.ApiUploadStandaloneFile(user.userId, file), ctrl) + return if (r is CR.SndStandaloneFileCreated) { + r.fileTransferMeta to null + } else { + Log.e(TAG, "uploadStandaloneFile error: $r") + null to r.toString() + } + } + + suspend fun downloadStandaloneFile(user: UserLike, url: String, file: CryptoFile, ctrl: ChatCtrl? = null): Pair<RcvFileTransfer?, String?> { + val r = sendCmd(null, CC.ApiDownloadStandaloneFile(user.userId, url, file), ctrl) + return if (r is CR.RcvStandaloneFileCreated) { + r.rcvFileTransfer to null + } else { + Log.e(TAG, "downloadStandaloneFile error: $r") + null to r.toString() + } + } + + suspend fun standaloneFileInfo(url: String, ctrl: ChatCtrl? = null): MigrationFileLinkData? { + val r = sendCmd(null, CC.ApiStandaloneFileInfo(url), ctrl) + return if (r is CR.StandaloneFileInfo) { + r.fileMeta + } else { + Log.e(TAG, "standaloneFileInfo error: $r") + null + } + } + suspend fun apiReceiveFile(rh: Long?, fileId: Long, encrypted: Boolean, inline: Boolean? = null, auto: Boolean = false): AChatItem? { // -1 here is to override default behavior of providing current remote host id because file can be asked by local device while remote is connected val r = sendCmd(rh, CC.ReceiveFile(fileId, encrypted, inline)) @@ -1263,11 +1348,11 @@ object ChatController { } } - suspend fun apiCancelFile(rh: Long?, fileId: Long): AChatItem? { - val r = sendCmd(rh, CC.CancelFile(fileId)) + suspend fun apiCancelFile(rh: Long?, fileId: Long, ctrl: ChatCtrl? = null): AChatItem? { + val r = sendCmd(rh, CC.CancelFile(fileId), ctrl) return when (r) { - is CR.SndFileCancelled -> r.chatItem - is CR.RcvFileCancelled -> r.chatItem + is CR.SndFileCancelled -> r.chatItem_ + is CR.RcvFileCancelled -> r.chatItem_ else -> { Log.d(TAG, "apiCancelFile bad response: ${r.responseType} ${r.details}") null @@ -1554,8 +1639,8 @@ object ChatController { suspend fun deleteRemoteCtrl(rcId: Long): Boolean = sendCommandOkResp(null, CC.DeleteRemoteCtrl(rcId)) - private suspend fun sendCommandOkResp(rh: Long?, cmd: CC): Boolean { - val r = sendCmd(rh, cmd) + private suspend fun sendCommandOkResp(rh: Long?, cmd: CC, ctrl: ChatCtrl? = null): Boolean { + val r = sendCmd(rh, cmd, ctrl) val ok = r is CR.CmdOk if (!ok) apiErrorAlert(cmd.cmdType, generalGetString(MR.strings.error_alert_title), r) return ok @@ -1845,11 +1930,16 @@ object ChatController { chatItemSimpleUpdate(rhId, r.user, r.chatItem) cleanupFile(r.chatItem) } - is CR.RcvFileProgressXFTP -> - chatItemSimpleUpdate(rhId, r.user, r.chatItem) + is CR.RcvFileProgressXFTP -> { + if (r.chatItem_ != null) { + chatItemSimpleUpdate(rhId, r.user, r.chatItem_) + } + } is CR.RcvFileError -> { - chatItemSimpleUpdate(rhId, r.user, r.chatItem) - cleanupFile(r.chatItem) + if (r.chatItem_ != null) { + chatItemSimpleUpdate(rhId, r.user, r.chatItem_) + cleanupFile(r.chatItem_) + } } is CR.SndFileStart -> chatItemSimpleUpdate(rhId, r.user, r.chatItem) @@ -1858,18 +1948,25 @@ object ChatController { cleanupDirectFile(r.chatItem) } is CR.SndFileRcvCancelled -> { - chatItemSimpleUpdate(rhId, r.user, r.chatItem) - cleanupDirectFile(r.chatItem) + if (r.chatItem_ != null) { + chatItemSimpleUpdate(rhId, r.user, r.chatItem_) + cleanupDirectFile(r.chatItem_) + } + } + is CR.SndFileProgressXFTP -> { + if (r.chatItem_ != null) { + chatItemSimpleUpdate(rhId, r.user, r.chatItem_) + } } - is CR.SndFileProgressXFTP -> - chatItemSimpleUpdate(rhId, r.user, r.chatItem) is CR.SndFileCompleteXFTP -> { chatItemSimpleUpdate(rhId, r.user, r.chatItem) cleanupFile(r.chatItem) } is CR.SndFileError -> { - chatItemSimpleUpdate(rhId, r.user, r.chatItem) - cleanupFile(r.chatItem) + if (r.chatItem_ != null) { + chatItemSimpleUpdate(rhId, r.user, r.chatItem_) + cleanupFile(r.chatItem_) + } } is CR.CallInvitation -> { chatModel.callManager.reportNewIncomingCall(r.callInvitation.copy(remoteHostId = rhId)) @@ -1908,10 +2005,8 @@ object ChatController { if (invitation != null) { chatModel.callManager.reportCallRemoteEnded(invitation = invitation) } - withCall(r, r.contact) { _ -> - chatModel.callCommand.add(WCallCommand.End) - chatModel.activeCall.value = null - chatModel.showCallView.value = false + withCall(r, r.contact) { call -> + withBGApi { chatModel.callManager.endCall(call) } } } is CR.ContactSwitch -> @@ -2018,6 +2113,10 @@ object ChatController { } } } + is CR.ContactPQEnabled -> + if (active(r.user)) { + chatModel.updateContact(rhId, r.contact) + } is CR.ChatCmdError -> when { r.chatError is ChatError.ChatErrorAgent && r.chatError.agentError is AgentErrorType.CRITICAL -> { chatModel.processedCriticalError.newError(r.chatError.agentError, r.chatError.agentError.offerRestart) @@ -2236,21 +2335,13 @@ object ChatController { class SharedPreference<T>(val get: () -> T, set: (T) -> Unit) { val set: (T) -> Unit - private val _state: MutableState<T> by lazy { mutableStateOf(get()) } - val state: State<T> by lazy { _state } + private val _state: MutableState<T> = mutableStateOf(get()) + val state: State<T> = _state init { this.set = { value -> set(value) - try { - _state.value = value - } catch (e: IllegalStateException) { - // Can be `Reading a state that was created after the snapshot was taken or in a snapshot that has not yet been applied` - Log.i(TAG, e.stackTraceToString()) - withApi { - _state.value = value - } - } + _state.value = value } } } @@ -2276,10 +2367,15 @@ sealed class CC { class SetFilesFolder(val filesFolder: String): CC() class SetRemoteHostsFolder(val remoteHostsFolder: String): CC() class ApiSetEncryptLocalFiles(val enable: Boolean): CC() + class ApiSetPQEncryption(val enable: Boolean): CC() + class ApiSetContactPQ(val contactId: Long, val enable: Boolean): CC() class ApiExportArchive(val config: ArchiveConfig): CC() class ApiImportArchive(val config: ArchiveConfig): CC() class ApiDeleteStorage: CC() class ApiStorageEncryption(val config: DBEncryptionConfig): CC() + class TestStorageEncryption(val key: String): CC() + class ApiSaveSettings(val settings: AppSettings): CC() + class ApiGetSettings(val settings: AppSettings): CC() class ApiGetChats(val userId: Long): CC() class ApiGetChat(val type: ChatType, val id: Long, val pagination: ChatPagination, val search: String = ""): CC() class ApiGetChatItemInfo(val type: ChatType, val id: Long, val itemId: Long): CC() @@ -2373,6 +2469,9 @@ sealed class CC { class ListRemoteCtrls(): CC() class StopRemoteCtrl(): CC() class DeleteRemoteCtrl(val remoteCtrlId: Long): CC() + class ApiUploadStandaloneFile(val userId: Long, val file: CryptoFile): CC() + class ApiDownloadStandaloneFile(val userId: Long, val url: String, val file: CryptoFile): CC() + class ApiStandaloneFileInfo(val url: String): CC() // misc class ShowVersion(): CC() @@ -2405,10 +2504,15 @@ sealed class CC { is SetFilesFolder -> "/_files_folder $filesFolder" is SetRemoteHostsFolder -> "/remote_hosts_folder $remoteHostsFolder" is ApiSetEncryptLocalFiles -> "/_files_encrypt ${onOff(enable)}" + is ApiSetPQEncryption -> "/pq ${onOff(enable)}" + is ApiSetContactPQ -> "/_pq @$contactId ${onOff(enable)}" is ApiExportArchive -> "/_db export ${json.encodeToString(config)}" is ApiImportArchive -> "/_db import ${json.encodeToString(config)}" is ApiDeleteStorage -> "/_db delete" is ApiStorageEncryption -> "/_db encryption ${json.encodeToString(config)}" + is TestStorageEncryption -> "/db test key $key" + is ApiSaveSettings -> "/_save app settings ${json.encodeToString(settings)}" + is ApiGetSettings -> "/_get app settings ${json.encodeToString(settings)}" is ApiGetChats -> "/_get chats $userId pcc=on" is ApiGetChat -> "/_get chat ${chatRef(type, id)} ${pagination.cmdString}" + (if (search == "") "" else " search=$search") is ApiGetChatItemInfo -> "/_get item info ${chatRef(type, id)} $itemId" @@ -2516,6 +2620,9 @@ sealed class CC { is ListRemoteCtrls -> "/list remote ctrls" is StopRemoteCtrl -> "/stop remote ctrl" is DeleteRemoteCtrl -> "/delete remote ctrl $remoteCtrlId" + is ApiUploadStandaloneFile -> "/_upload $userId ${file.filePath}" + is ApiDownloadStandaloneFile -> "/_download $userId $url ${file.filePath}" + is ApiStandaloneFileInfo -> "/_download info $url" is ShowVersion -> "/version" } @@ -2539,10 +2646,15 @@ sealed class CC { is SetFilesFolder -> "setFilesFolder" is SetRemoteHostsFolder -> "setRemoteHostsFolder" is ApiSetEncryptLocalFiles -> "apiSetEncryptLocalFiles" + is ApiSetPQEncryption -> "apiSetPQEncryption" + is ApiSetContactPQ -> "apiSetContactPQ" is ApiExportArchive -> "apiExportArchive" is ApiImportArchive -> "apiImportArchive" is ApiDeleteStorage -> "apiDeleteStorage" is ApiStorageEncryption -> "apiStorageEncryption" + is TestStorageEncryption -> "testStorageEncryption" + is ApiSaveSettings -> "apiSaveSettings" + is ApiGetSettings -> "apiGetSettings" is ApiGetChats -> "apiGetChats" is ApiGetChat -> "apiGetChat" is ApiGetChatItemInfo -> "apiGetChatItemInfo" @@ -2635,6 +2747,9 @@ sealed class CC { is ListRemoteCtrls -> "listRemoteCtrls" is StopRemoteCtrl -> "stopRemoteCtrl" is DeleteRemoteCtrl -> "deleteRemoteCtrl" + is ApiUploadStandaloneFile -> "apiUploadStandaloneFile" + is ApiDownloadStandaloneFile -> "apiDownloadStandaloneFile" + is ApiStandaloneFileInfo -> "apiStandaloneFileInfo" is ShowVersion -> "showVersion" } @@ -2652,6 +2767,7 @@ sealed class CC { is ApiHideUser -> ApiHideUser(userId, obfuscate(viewPwd)) is ApiUnhideUser -> ApiUnhideUser(userId, obfuscate(viewPwd)) is ApiDeleteUser -> ApiDeleteUser(userId, delSMPQueues, obfuscateOrNull(viewPwd)) + is TestStorageEncryption -> TestStorageEncryption(obfuscate(key)) else -> this } @@ -3777,6 +3893,13 @@ val json = Json { explicitNulls = false } +val jsonShort = Json { + prettyPrint = false + ignoreUnknownKeys = true + encodeDefaults = true + explicitNulls = false +} + val yaml = Yaml(configuration = YamlConfiguration( strictMode = false, encodeDefaults = false, @@ -3966,20 +4089,28 @@ sealed class CR { // receiving file events @Serializable @SerialName("rcvFileAccepted") class RcvFileAccepted(val user: UserRef, val chatItem: AChatItem): CR() @Serializable @SerialName("rcvFileAcceptedSndCancelled") class RcvFileAcceptedSndCancelled(val user: UserRef, val rcvFileTransfer: RcvFileTransfer): CR() - @Serializable @SerialName("rcvFileStart") class RcvFileStart(val user: UserRef, val chatItem: AChatItem): CR() + @Serializable @SerialName("standaloneFileInfo") class StandaloneFileInfo(val fileMeta: MigrationFileLinkData?): CR() + @Serializable @SerialName("rcvStandaloneFileCreated") class RcvStandaloneFileCreated(val user: UserRef, val rcvFileTransfer: RcvFileTransfer): CR() + @Serializable @SerialName("rcvFileStart") class RcvFileStart(val user: UserRef, val chatItem: AChatItem): CR() // send by chats + @Serializable @SerialName("rcvFileProgressXFTP") class RcvFileProgressXFTP(val user: UserRef, val chatItem_: AChatItem?, val receivedSize: Long, val totalSize: Long, val rcvFileTransfer: RcvFileTransfer): CR() @Serializable @SerialName("rcvFileComplete") class RcvFileComplete(val user: UserRef, val chatItem: AChatItem): CR() - @Serializable @SerialName("rcvFileCancelled") class RcvFileCancelled(val user: UserRef, val chatItem: AChatItem, val rcvFileTransfer: RcvFileTransfer): CR() + @Serializable @SerialName("rcvStandaloneFileComplete") class RcvStandaloneFileComplete(val user: UserRef, val targetPath: String, val rcvFileTransfer: RcvFileTransfer): CR() + @Serializable @SerialName("rcvFileCancelled") class RcvFileCancelled(val user: UserRef, val chatItem_: AChatItem?, val rcvFileTransfer: RcvFileTransfer): CR() @Serializable @SerialName("rcvFileSndCancelled") class RcvFileSndCancelled(val user: UserRef, val chatItem: AChatItem, val rcvFileTransfer: RcvFileTransfer): CR() - @Serializable @SerialName("rcvFileProgressXFTP") class RcvFileProgressXFTP(val user: UserRef, val chatItem: AChatItem, val receivedSize: Long, val totalSize: Long): CR() - @Serializable @SerialName("rcvFileError") class RcvFileError(val user: UserRef, val chatItem: AChatItem): CR() + @Serializable @SerialName("rcvFileError") class RcvFileError(val user: UserRef, val chatItem_: AChatItem?, val rcvFileTransfer: RcvFileTransfer): CR() // sending file events @Serializable @SerialName("sndFileStart") class SndFileStart(val user: UserRef, val chatItem: AChatItem, val sndFileTransfer: SndFileTransfer): CR() @Serializable @SerialName("sndFileComplete") class SndFileComplete(val user: UserRef, val chatItem: AChatItem, val sndFileTransfer: SndFileTransfer): CR() - @Serializable @SerialName("sndFileCancelled") class SndFileCancelled(val user: UserRef, val chatItem: AChatItem, val fileTransferMeta: FileTransferMeta, val sndFileTransfers: List<SndFileTransfer>): CR() - @Serializable @SerialName("sndFileRcvCancelled") class SndFileRcvCancelled(val user: UserRef, val chatItem: AChatItem, val sndFileTransfer: SndFileTransfer): CR() - @Serializable @SerialName("sndFileProgressXFTP") class SndFileProgressXFTP(val user: UserRef, val chatItem: AChatItem, val fileTransferMeta: FileTransferMeta, val sentSize: Long, val totalSize: Long): CR() + @Serializable @SerialName("sndFileRcvCancelled") class SndFileRcvCancelled(val user: UserRef, val chatItem_: AChatItem?, val sndFileTransfer: SndFileTransfer): CR() + @Serializable @SerialName("sndFileCancelled") class SndFileCancelled(val user: UserRef, val chatItem_: AChatItem?, val fileTransferMeta: FileTransferMeta, val sndFileTransfers: List<SndFileTransfer>): CR() + @Serializable @SerialName("sndStandaloneFileCreated") class SndStandaloneFileCreated(val user: UserRef, val fileTransferMeta: FileTransferMeta): CR() // returned by _upload + @Serializable @SerialName("sndFileStartXFTP") class SndFileStartXFTP(val user: UserRef, val chatItem: AChatItem, val fileTransferMeta: FileTransferMeta): CR() // not used + @Serializable @SerialName("sndFileProgressXFTP") class SndFileProgressXFTP(val user: UserRef, val chatItem_: AChatItem?, val fileTransferMeta: FileTransferMeta, val sentSize: Long, val totalSize: Long): CR() + @Serializable @SerialName("sndFileRedirectStartXFTP") class SndFileRedirectStartXFTP(val user: UserRef, val fileTransferMeta: FileTransferMeta, val redirectMeta: FileTransferMeta): CR() @Serializable @SerialName("sndFileCompleteXFTP") class SndFileCompleteXFTP(val user: UserRef, val chatItem: AChatItem, val fileTransferMeta: FileTransferMeta): CR() - @Serializable @SerialName("sndFileError") class SndFileError(val user: UserRef, val chatItem: AChatItem): CR() + @Serializable @SerialName("sndStandaloneFileComplete") class SndStandaloneFileComplete(val user: UserRef, val fileTransferMeta: FileTransferMeta, val rcvURIs: List<String>): CR() + @Serializable @SerialName("sndFileCancelledXFTP") class SndFileCancelledXFTP(val user: UserRef, val chatItem_: AChatItem?, val fileTransferMeta: FileTransferMeta): CR() + @Serializable @SerialName("sndFileError") class SndFileError(val user: UserRef, val chatItem_: AChatItem?, val fileTransferMeta: FileTransferMeta): CR() // call events @Serializable @SerialName("callInvitation") class CallInvitation(val callInvitation: RcvCallInvitation): CR() @Serializable @SerialName("callInvitations") class CallInvitations(val callInvitations: List<RcvCallInvitation>): CR() @@ -4004,11 +4135,16 @@ sealed class CR { @Serializable @SerialName("remoteCtrlSessionCode") class RemoteCtrlSessionCode(val remoteCtrl_: RemoteCtrlInfo?, val sessionCode: String): CR() @Serializable @SerialName("remoteCtrlConnected") class RemoteCtrlConnected(val remoteCtrl: RemoteCtrlInfo): CR() @Serializable @SerialName("remoteCtrlStopped") class RemoteCtrlStopped(val rcsState: RemoteCtrlSessionState, val rcStopReason: RemoteCtrlStopReason): CR() + // pq + @Serializable @SerialName("contactPQAllowed") class ContactPQAllowed(val user: UserRef, val contact: Contact, val pqEncryption: Boolean): CR() + @Serializable @SerialName("contactPQEnabled") class ContactPQEnabled(val user: UserRef, val contact: Contact, val pqEnabled: Boolean): CR() + // misc @Serializable @SerialName("versionInfo") class VersionInfo(val versionInfo: CoreVersionInfo, val chatMigrations: List<UpMigration>, val agentMigrations: List<UpMigration>): CR() @Serializable @SerialName("cmdOk") class CmdOk(val user: UserRef?): CR() @Serializable @SerialName("chatCmdError") class ChatCmdError(val user_: UserRef?, val chatError: ChatError): CR() @Serializable @SerialName("chatError") class ChatRespError(val user_: UserRef?, val chatError: ChatError): CR() @Serializable @SerialName("archiveImported") class ArchiveImported(val archiveErrors: List<ArchiveError>): CR() + @Serializable @SerialName("appSettings") class AppSettingsR(val appSettings: AppSettings): CR() // general @Serializable class Response(val type: String, val json: String): CR() @Serializable class Invalid(val str: String): CR() @@ -4118,19 +4254,27 @@ sealed class CR { is NewMemberContactSentInv -> "newMemberContactSentInv" is NewMemberContactReceivedInv -> "newMemberContactReceivedInv" is RcvFileAcceptedSndCancelled -> "rcvFileAcceptedSndCancelled" + is StandaloneFileInfo -> "standaloneFileInfo" + is RcvStandaloneFileCreated -> "rcvStandaloneFileCreated" is RcvFileAccepted -> "rcvFileAccepted" is RcvFileStart -> "rcvFileStart" is RcvFileComplete -> "rcvFileComplete" + is RcvStandaloneFileComplete -> "rcvStandaloneFileComplete" is RcvFileCancelled -> "rcvFileCancelled" + is SndStandaloneFileCreated -> "sndStandaloneFileCreated" + is SndFileStartXFTP -> "sndFileStartXFTP" is RcvFileSndCancelled -> "rcvFileSndCancelled" is RcvFileProgressXFTP -> "rcvFileProgressXFTP" + is SndFileRedirectStartXFTP -> "sndFileRedirectStartXFTP" is RcvFileError -> "rcvFileError" - is SndFileCancelled -> "sndFileCancelled" + is SndFileStart -> "sndFileStart" is SndFileComplete -> "sndFileComplete" is SndFileRcvCancelled -> "sndFileRcvCancelled" - is SndFileStart -> "sndFileStart" + is SndFileCancelled -> "sndFileCancelled" is SndFileProgressXFTP -> "sndFileProgressXFTP" is SndFileCompleteXFTP -> "sndFileCompleteXFTP" + is SndStandaloneFileComplete -> "sndStandaloneFileComplete" + is SndFileCancelledXFTP -> "sndFileCancelledXFTP" is SndFileError -> "sndFileError" is CallInvitations -> "callInvitations" is CallInvitation -> "callInvitation" @@ -4153,11 +4297,14 @@ sealed class CR { is RemoteCtrlSessionCode -> "remoteCtrlSessionCode" is RemoteCtrlConnected -> "remoteCtrlConnected" is RemoteCtrlStopped -> "remoteCtrlStopped" + is ContactPQAllowed -> "contactPQAllowed" + is ContactPQEnabled -> "contactPQEnabled" is VersionInfo -> "versionInfo" is CmdOk -> "cmdOk" is ChatCmdError -> "chatCmdError" is ChatRespError -> "chatError" is ArchiveImported -> "archiveImported" + is AppSettingsR -> "appSettings" is Response -> "* $type" is Invalid -> "* invalid json" } @@ -4170,7 +4317,7 @@ sealed class CR { is ChatStopped -> noDetails() is ApiChats -> withUser(user, json.encodeToString(chats)) is ApiChat -> withUser(user, json.encodeToString(chat)) - is ApiChatItemInfo -> withUser(user, "chatItem: ${json.encodeToString(AChatItem)}\n${json.encodeToString(chatItemInfo)}") + is ApiChatItemInfo -> withUser(user, "chatItem: ${json.encodeToString(chatItem)}\n${json.encodeToString(chatItemInfo)}") is UserProtoServers -> withUser(user, "servers: ${json.encodeToString(servers)}") is ServerTestResult -> withUser(user, "server: $testServer\nresult: ${json.encodeToString(testFailure)}") is ChatItemTTL -> withUser(user, json.encodeToString(chatItemTTL)) @@ -4267,20 +4414,28 @@ sealed class CR { is NewMemberContactSentInv -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member") is NewMemberContactReceivedInv -> withUser(user, "contact: $contact\ngroupInfo: $groupInfo\nmember: $member") is RcvFileAcceptedSndCancelled -> withUser(user, noDetails()) + is StandaloneFileInfo -> json.encodeToString(fileMeta) + is RcvStandaloneFileCreated -> noDetails() is RcvFileAccepted -> withUser(user, json.encodeToString(chatItem)) is RcvFileStart -> withUser(user, json.encodeToString(chatItem)) is RcvFileComplete -> withUser(user, json.encodeToString(chatItem)) - is RcvFileCancelled -> withUser(user, json.encodeToString(chatItem)) + is RcvFileCancelled -> withUser(user, json.encodeToString(chatItem_)) is RcvFileSndCancelled -> withUser(user, json.encodeToString(chatItem)) - is RcvFileProgressXFTP -> withUser(user, "chatItem: ${json.encodeToString(chatItem)}\nreceivedSize: $receivedSize\ntotalSize: $totalSize") - is RcvFileError -> withUser(user, json.encodeToString(chatItem)) - is SndFileCancelled -> json.encodeToString(chatItem) + is RcvFileProgressXFTP -> withUser(user, "chatItem: ${json.encodeToString(chatItem_)}\nreceivedSize: $receivedSize\ntotalSize: $totalSize") + is RcvStandaloneFileComplete -> withUser(user, targetPath) + is RcvFileError -> withUser(user, json.encodeToString(chatItem_)) + is SndFileCancelled -> json.encodeToString(chatItem_) + is SndStandaloneFileCreated -> noDetails() + is SndFileStartXFTP -> withUser(user, json.encodeToString(chatItem)) is SndFileComplete -> withUser(user, json.encodeToString(chatItem)) - is SndFileRcvCancelled -> withUser(user, json.encodeToString(chatItem)) + is SndFileRcvCancelled -> withUser(user, json.encodeToString(chatItem_)) is SndFileStart -> withUser(user, json.encodeToString(chatItem)) - is SndFileProgressXFTP -> withUser(user, "chatItem: ${json.encodeToString(chatItem)}\nsentSize: $sentSize\ntotalSize: $totalSize") + is SndFileProgressXFTP -> withUser(user, "chatItem: ${json.encodeToString(chatItem_)}\nsentSize: $sentSize\ntotalSize: $totalSize") + is SndFileRedirectStartXFTP -> withUser(user, json.encodeToString(redirectMeta)) is SndFileCompleteXFTP -> withUser(user, json.encodeToString(chatItem)) - is SndFileError -> withUser(user, json.encodeToString(chatItem)) + is SndStandaloneFileComplete -> withUser(user, rcvURIs.size.toString()) + is SndFileCancelledXFTP -> withUser(user, json.encodeToString(chatItem_)) + is SndFileError -> withUser(user, json.encodeToString(chatItem_)) is CallInvitations -> "callInvitations: ${json.encodeToString(callInvitations)}" is CallInvitation -> "contact: ${callInvitation.contact.id}\ncallType: $callInvitation.callType\nsharedKey: ${callInvitation.sharedKey ?: ""}" is CallOffer -> withUser(user, "contact: ${contact.id}\ncallType: $callType\nsharedKey: ${sharedKey ?: ""}\naskConfirmation: $askConfirmation\noffer: ${json.encodeToString(offer)}") @@ -4317,6 +4472,8 @@ sealed class CR { "\nsessionCode: $sessionCode" is RemoteCtrlConnected -> json.encodeToString(remoteCtrl) is RemoteCtrlStopped -> noDetails() + is ContactPQAllowed -> withUser(user, "contact: ${contact.id}\npqEncryption: $pqEncryption") + is ContactPQEnabled -> withUser(user, "contact: ${contact.id}\npqEnabled: $pqEnabled") is VersionInfo -> "version ${json.encodeToString(versionInfo)}\n\n" + "chat migrations: ${json.encodeToString(chatMigrations.map { it.upName })}\n\n" + "agent migrations: ${json.encodeToString(agentMigrations.map { it.upName })}" @@ -4324,6 +4481,7 @@ sealed class CR { is ChatCmdError -> withUser(user_, chatError.string) is ChatRespError -> withUser(user_, chatError.string) is ArchiveImported -> "${archiveErrors.map { it.string } }" + is AppSettingsR -> json.encodeToString(appSettings) is Response -> json is Invalid -> str } @@ -4737,6 +4895,7 @@ sealed class StoreError { is FileIdNotFoundBySharedMsgId -> "fileIdNotFoundBySharedMsgId" is SndFileNotFoundXFTP -> "sndFileNotFoundXFTP" is RcvFileNotFoundXFTP -> "rcvFileNotFoundXFTP" + is ExtraFileDescrNotFoundXFTP -> "extraFileDescrNotFoundXFTP" is ConnectionNotFound -> "connectionNotFound" is ConnectionNotFoundById -> "connectionNotFoundById" is ConnectionNotFoundByMemberId -> "connectionNotFoundByMemberId" @@ -4795,6 +4954,7 @@ sealed class StoreError { @Serializable @SerialName("fileIdNotFoundBySharedMsgId") class FileIdNotFoundBySharedMsgId(val sharedMsgId: String): StoreError() @Serializable @SerialName("sndFileNotFoundXFTP") class SndFileNotFoundXFTP(val agentSndFileId: String): StoreError() @Serializable @SerialName("rcvFileNotFoundXFTP") class RcvFileNotFoundXFTP(val agentRcvFileId: String): StoreError() + @Serializable @SerialName("extraFileDescrNotFoundXFTP") class ExtraFileDescrNotFoundXFTP(val fileId: Long): StoreError() @Serializable @SerialName("connectionNotFound") class ConnectionNotFound(val agentConnId: String): StoreError() @Serializable @SerialName("connectionNotFoundById") class ConnectionNotFoundById(val connId: Long): StoreError() @Serializable @SerialName("connectionNotFoundByMemberId") class ConnectionNotFoundByMemberId(val groupMemberId: Long): StoreError() @@ -5140,3 +5300,205 @@ enum class NotificationsMode() { val default: NotificationsMode = SERVICE } } + +@Serializable +data class AppSettings( + var networkConfig: NetCfg? = null, + var privacyEncryptLocalFiles: Boolean? = null, + var privacyAcceptImages: Boolean? = null, + var privacyLinkPreviews: Boolean? = null, + var privacyShowChatPreviews: Boolean? = null, + var privacySaveLastDraft: Boolean? = null, + var privacyProtectScreen: Boolean? = null, + var notificationMode: AppSettingsNotificationMode? = null, + var notificationPreviewMode: AppSettingsNotificationPreviewMode? = null, + var webrtcPolicyRelay: Boolean? = null, + var webrtcICEServers: List<String>? = null, + var confirmRemoteSessions: Boolean? = null, + var connectRemoteViaMulticast: Boolean? = null, + var connectRemoteViaMulticastAuto: Boolean? = null, + var developerTools: Boolean? = null, + var confirmDBUpgrades: Boolean? = null, + var androidCallOnLockScreen: AppSettingsLockScreenCalls? = null, + var iosCallKitEnabled: Boolean? = null, + var iosCallKitCallsInRecents: Boolean? = null, +) { + fun prepareForExport(): AppSettings { + val empty = AppSettings() + val def = defaults + if (networkConfig != def.networkConfig) { empty.networkConfig = networkConfig } + if (privacyEncryptLocalFiles != def.privacyEncryptLocalFiles) { empty.privacyEncryptLocalFiles = privacyEncryptLocalFiles } + if (privacyAcceptImages != def.privacyAcceptImages) { empty.privacyAcceptImages = privacyAcceptImages } + if (privacyLinkPreviews != def.privacyLinkPreviews) { empty.privacyLinkPreviews = privacyLinkPreviews } + if (privacyShowChatPreviews != def.privacyShowChatPreviews) { empty.privacyShowChatPreviews = privacyShowChatPreviews } + if (privacySaveLastDraft != def.privacySaveLastDraft) { empty.privacySaveLastDraft = privacySaveLastDraft } + if (privacyProtectScreen != def.privacyProtectScreen) { empty.privacyProtectScreen = privacyProtectScreen } + if (notificationMode != def.notificationMode) { empty.notificationMode = notificationMode } + if (notificationPreviewMode != def.notificationPreviewMode) { empty.notificationPreviewMode = notificationPreviewMode } + if (webrtcPolicyRelay != def.webrtcPolicyRelay) { empty.webrtcPolicyRelay = webrtcPolicyRelay } + if (webrtcICEServers != def.webrtcICEServers) { empty.webrtcICEServers = webrtcICEServers } + if (confirmRemoteSessions != def.confirmRemoteSessions) { empty.confirmRemoteSessions = confirmRemoteSessions } + if (connectRemoteViaMulticast != def.connectRemoteViaMulticast) { empty.connectRemoteViaMulticast = connectRemoteViaMulticast } + if (connectRemoteViaMulticastAuto != def.connectRemoteViaMulticastAuto) { empty.connectRemoteViaMulticastAuto = connectRemoteViaMulticastAuto } + if (developerTools != def.developerTools) { empty.developerTools = developerTools } + if (confirmDBUpgrades != def.confirmDBUpgrades) { empty.confirmDBUpgrades = confirmDBUpgrades } + if (androidCallOnLockScreen != def.androidCallOnLockScreen) { empty.androidCallOnLockScreen = androidCallOnLockScreen } + if (iosCallKitEnabled != def.iosCallKitEnabled) { empty.iosCallKitEnabled = iosCallKitEnabled } + if (iosCallKitCallsInRecents != def.iosCallKitCallsInRecents) { empty.iosCallKitCallsInRecents = iosCallKitCallsInRecents } + return empty + } + + fun importIntoApp() { + val def = appPreferences + var net = networkConfig?.copy() + if (net != null) { + // migrating from iOS BUT shouldn't be here ever because it should be changed on migration stage + if (net.hostMode == HostMode.Onion) { + net = net.copy(hostMode = HostMode.Public, requiredHostMode = true) + } + setNetCfg(net) + } + privacyEncryptLocalFiles?.let { def.privacyEncryptLocalFiles.set(it) } + privacyAcceptImages?.let { def.privacyAcceptImages.set(it) } + privacyLinkPreviews?.let { def.privacyLinkPreviews.set(it) } + privacyShowChatPreviews?.let { def.privacyShowChatPreviews.set(it) } + privacySaveLastDraft?.let { def.privacySaveLastDraft.set(it) } + privacyProtectScreen?.let { def.privacyProtectScreen.set(it) } + notificationMode?.let { def.notificationsMode.set(it.toNotificationsMode()) } + notificationPreviewMode?.let { def.notificationPreviewMode.set(it.toNotificationPreviewMode().name) } + webrtcPolicyRelay?.let { def.webrtcPolicyRelay.set(it) } + webrtcICEServers?.let { def.webrtcIceServers.set(it.joinToString(separator = "\n")) } + confirmRemoteSessions?.let { def.confirmRemoteSessions.set(it) } + connectRemoteViaMulticast?.let { def.connectRemoteViaMulticast.set(it) } + connectRemoteViaMulticastAuto?.let { def.connectRemoteViaMulticastAuto.set(it) } + developerTools?.let { def.developerTools.set(it) } + confirmDBUpgrades?.let { def.confirmDBUpgrades.set(it) } + androidCallOnLockScreen?.let { def.callOnLockScreen.set(it.toCallOnLockScreen()) } + iosCallKitEnabled?.let { def.iosCallKitEnabled.set(it) } + iosCallKitCallsInRecents?.let { def.iosCallKitCallsInRecents.set(it) } + } + + companion object { + val defaults: AppSettings + get() = AppSettings( + networkConfig = NetCfg.defaults, + privacyEncryptLocalFiles = true, + privacyAcceptImages = true, + privacyLinkPreviews = true, + privacyShowChatPreviews = true, + privacySaveLastDraft = true, + privacyProtectScreen = false, + notificationMode = AppSettingsNotificationMode.INSTANT, + notificationPreviewMode = AppSettingsNotificationPreviewMode.MESSAGE, + webrtcPolicyRelay = true, + webrtcICEServers = emptyList(), + confirmRemoteSessions = false, + connectRemoteViaMulticast = true, + connectRemoteViaMulticastAuto = true, + developerTools = false, + confirmDBUpgrades = false, + androidCallOnLockScreen = AppSettingsLockScreenCalls.SHOW, + iosCallKitEnabled = true, + iosCallKitCallsInRecents = false + ) + + val current: AppSettings + get() { + val def = appPreferences + return defaults.copy( + networkConfig = getNetCfg(), + privacyEncryptLocalFiles = def.privacyEncryptLocalFiles.get(), + privacyAcceptImages = def.privacyAcceptImages.get(), + privacyLinkPreviews = def.privacyLinkPreviews.get(), + privacyShowChatPreviews = def.privacyShowChatPreviews.get(), + privacySaveLastDraft = def.privacySaveLastDraft.get(), + privacyProtectScreen = def.privacyProtectScreen.get(), + notificationMode = AppSettingsNotificationMode.from(def.notificationsMode.get()), + notificationPreviewMode = AppSettingsNotificationPreviewMode.from(NotificationPreviewMode.valueOf(def.notificationPreviewMode.get()!!)), + webrtcPolicyRelay = def.webrtcPolicyRelay.get(), + webrtcICEServers = def.webrtcIceServers.get()?.lines(), + confirmRemoteSessions = def.confirmRemoteSessions.get(), + connectRemoteViaMulticast = def.connectRemoteViaMulticast.get(), + connectRemoteViaMulticastAuto = def.connectRemoteViaMulticastAuto.get(), + developerTools = def.developerTools.get(), + confirmDBUpgrades = def.confirmDBUpgrades.get(), + androidCallOnLockScreen = AppSettingsLockScreenCalls.from(def.callOnLockScreen.get()), + iosCallKitEnabled = def.iosCallKitEnabled.get(), + iosCallKitCallsInRecents = def.iosCallKitCallsInRecents.get(), + ) + } + } +} + +@Serializable +enum class AppSettingsNotificationMode { + @SerialName("off") OFF, + @SerialName("periodic") PERIODIC, + @SerialName("instant") INSTANT; + + fun toNotificationsMode(): NotificationsMode = + when (this) { + INSTANT -> NotificationsMode.SERVICE + PERIODIC -> NotificationsMode.PERIODIC + OFF -> NotificationsMode.OFF + } + + companion object { + fun from(mode: NotificationsMode): AppSettingsNotificationMode = + when (mode) { + NotificationsMode.SERVICE -> INSTANT + NotificationsMode.PERIODIC -> PERIODIC + NotificationsMode.OFF -> OFF + } + } +} + +@Serializable +enum class AppSettingsNotificationPreviewMode { + @SerialName("message") MESSAGE, + @SerialName("contact") CONTACT, + @SerialName("hidden") HIDDEN; + + fun toNotificationPreviewMode(): NotificationPreviewMode = + when (this) { + MESSAGE -> NotificationPreviewMode.MESSAGE + CONTACT -> NotificationPreviewMode.CONTACT + HIDDEN -> NotificationPreviewMode.HIDDEN + } + + companion object { + val default: AppSettingsNotificationPreviewMode = MESSAGE + + fun from(mode: NotificationPreviewMode): AppSettingsNotificationPreviewMode = + when (mode) { + NotificationPreviewMode.MESSAGE -> MESSAGE + NotificationPreviewMode.CONTACT -> CONTACT + NotificationPreviewMode.HIDDEN -> HIDDEN + } + } +} + +@Serializable +enum class AppSettingsLockScreenCalls { + @SerialName("disable") DISABLE, + @SerialName("show") SHOW, + @SerialName("accept") ACCEPT; + + fun toCallOnLockScreen(): CallOnLockScreen = + when (this) { + DISABLE -> CallOnLockScreen.DISABLE + SHOW -> CallOnLockScreen.SHOW + ACCEPT -> CallOnLockScreen.ACCEPT + } + + companion object { + val default = SHOW + + fun from(mode: CallOnLockScreen): AppSettingsLockScreenCalls = + when (mode) { + CallOnLockScreen.DISABLE -> DISABLE + CallOnLockScreen.SHOW -> SHOW + CallOnLockScreen.ACCEPT -> ACCEPT + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt index 7a7c2d7f24..f1a6d35e45 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Core.kt @@ -5,10 +5,12 @@ import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.model.ChatModel.currentUser import chat.simplex.common.views.helpers.* import chat.simplex.common.views.helpers.DatabaseUtils.ksDatabasePassword +import chat.simplex.common.views.helpers.DatabaseUtils.randomDatabasePassword import chat.simplex.common.views.onboarding.OnboardingStage import chat.simplex.res.MR import kotlinx.coroutines.* import kotlinx.serialization.decodeFromString +import java.io.File import java.nio.ByteBuffer // ghc's rts @@ -92,6 +94,7 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat controller.apiSetRemoteHostsFolder(remoteHostsDir.absolutePath) } controller.apiSetEncryptLocalFiles(controller.appPrefs.privacyEncryptLocalFiles.get()) + controller.apiSetPQEncryption(controller.appPrefs.pqExperimentalEnabled.get()) // If we migrated successfully means previous re-encryption process on database level finished successfully too if (appPreferences.encryptionStartedAt.get() != null) appPreferences.encryptionStartedAt.set(null) val user = chatController.apiGetActiveUser(null) @@ -136,6 +139,37 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat } } +fun chatInitTemporaryDatabase(dbPath: String, key: String? = null, confirmation: MigrationConfirmation = MigrationConfirmation.Error): Pair<DBMigrationResult, ChatCtrl?> { + val dbKey = key ?: randomDatabasePassword() + Log.d(TAG, "chatInitTemporaryDatabase path: $dbPath") + val migrated = chatMigrateInit(dbPath, dbKey, confirmation.value) + val res = runCatching { + json.decodeFromString<DBMigrationResult>(migrated[0] as String) + }.getOrElse { DBMigrationResult.Unknown(migrated[0] as String) } + + return res to migrated[1] as ChatCtrl +} + +fun chatInitControllerRemovingDatabases() { + val dbPath = dbAbsolutePrefixPath + // Remove previous databases, otherwise, can be .errorNotADatabase with null controller + File(dbPath + "_chat.db").delete() + File(dbPath + "_agent.db").delete() + + val dbKey = randomDatabasePassword() + Log.d(TAG, "chatInitControllerRemovingDatabases path: $dbPath") + val migrated = chatMigrateInit(dbPath, dbKey, MigrationConfirmation.Error.value) + val res = runCatching { + json.decodeFromString<DBMigrationResult>(migrated[0] as String) + }.getOrElse { DBMigrationResult.Unknown(migrated[0] as String) } + + val ctrl = migrated[1] as Long + chatController.ctrl = ctrl + // We need only controller, not databases + File(dbPath + "_chat.db").delete() + File(dbPath + "_agent.db").delete() +} + fun showStartChatAfterRestartAlert(): CompletableDeferred<Boolean> { val deferred = CompletableDeferred<Boolean>() AlertManager.shared.showAlertDialog( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt index a6c93cc2f3..f804f2079c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Files.kt @@ -42,7 +42,7 @@ fun copyFileToFile(from: File, to: URI, finally: () -> Unit) { } } showToast(generalGetString(MR.strings.file_saved)) - } catch (e: Error) { + } catch (e: Throwable) { showToast(generalGetString(MR.strings.error_saving_file)) Log.e(TAG, "copyFileToFile error saving file $e") } finally { @@ -58,7 +58,7 @@ fun copyBytesToFile(bytes: ByteArrayInputStream, to: URI, finally: () -> Unit) { } } showToast(generalGetString(MR.strings.file_saved)) - } catch (e: Error) { + } catch (e: Throwable) { showToast(generalGetString(MR.strings.error_saving_file)) Log.e(TAG, "copyBytesToFile error saving file $e") } finally { @@ -66,6 +66,8 @@ fun copyBytesToFile(bytes: ByteArrayInputStream, to: URI, finally: () -> Unit) { } } +fun getMigrationTempFilesDirectory(): File = File(dataDir, "migration_temp_files") + fun getAppFilePath(fileName: String): String { val rh = chatModel.currentRemoteHost.value val s = File.separator diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt index e55c2c939a..5ca6dbdeb3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Platform.kt @@ -1,17 +1,33 @@ package chat.simplex.common.platform +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D +import androidx.compose.foundation.ScrollState +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import chat.simplex.common.model.ChatId import chat.simplex.common.model.NotificationsMode +import kotlinx.coroutines.Job interface PlatformInterface { suspend fun androidServiceStart() {} fun androidServiceSafeStop() {} + fun androidCallServiceSafeStop() {} fun androidNotificationsModeChanged(mode: NotificationsMode) {} fun androidChatStartedAfterBeingOff() {} fun androidChatStopped() {} fun androidChatInitializedAndStarted() {} fun androidIsBackgroundCallAllowed(): Boolean = true fun androidSetNightModeIfSupported() {} + fun androidStartCallActivity(acceptCall: Boolean, remoteHostId: Long? = null, chatId: ChatId? = null) {} + fun androidPictureInPictureAllowed(): Boolean = true + fun androidCallEnded() {} + @Composable fun androidLockPortraitOrientation() {} suspend fun androidAskToAllowBackgroundCalls(): Boolean = true + @Composable fun desktopScrollBarComponents(): Triple<Animatable<Float, AnimationVector1D>, Modifier, MutableState<Job>> = remember { Triple(Animatable(0f), Modifier, mutableStateOf(Job())) } + @Composable fun desktopScrollBar(state: LazyListState, modifier: Modifier, scrollBarAlpha: Animatable<Float, AnimationVector1D>, scrollJob: MutableState<Job>, reversed: Boolean) {} + @Composable fun desktopScrollBar(state: ScrollState, modifier: Modifier, scrollBarAlpha: Animatable<Float, AnimationVector1D>, scrollJob: MutableState<Job>, reversed: Boolean) {} } /** * Multiplatform project has separate directories per platform + common directory that contains directories per platform + common for all of them. diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt new file mode 100644 index 0000000000..d4fa2fe125 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/ScrollableColumn.kt @@ -0,0 +1,34 @@ +package chat.simplex.common.platform + +import androidx.compose.foundation.* +import androidx.compose.foundation.gestures.FlingBehavior +import androidx.compose.foundation.gestures.ScrollableDefaults +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.unit.dp + +@Composable +expect fun LazyColumnWithScrollBar( + modifier: Modifier = Modifier, + state: LazyListState = rememberLazyListState(), + contentPadding: PaddingValues = PaddingValues(0.dp), + reverseLayout: Boolean = false, + verticalArrangement: Arrangement.Vertical = + if (!reverseLayout) Arrangement.Top else Arrangement.Bottom, + horizontalAlignment: Alignment.Horizontal = Alignment.Start, + flingBehavior: FlingBehavior = ScrollableDefaults.flingBehavior(), + userScrollEnabled: Boolean = true, + content: LazyListScope.() -> Unit +) + +@Composable +expect fun ColumnWithScrollBar( + modifier: Modifier = Modifier, + verticalArrangement: Arrangement.Vertical = Arrangement.Top, + horizontalAlignment: Alignment.Horizontal = Alignment.Start, + state: ScrollState = rememberScrollState(), + content: @Composable ColumnScope.() -> Unit +) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt index bcdf2e8380..1cd85ae7f9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/TerminalView.kt @@ -130,7 +130,7 @@ fun TerminalLog() { derivedStateOf { chatModel.terminalItems.value.asReversed() } } val clipboard = LocalClipboardManager.current - LazyColumn(state = listState, reverseLayout = true) { + LazyColumnWithScrollBar(state = listState, reverseLayout = true) { items(reversedTerminalItems) { item -> val rhId = item.remoteHostId val rhIdStr = if (rhId == null) "" else "$rhId " diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt index c4cbe06831..41b30be640 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/WelcomeView.kt @@ -49,8 +49,8 @@ fun CreateProfile(chatModel: ChatModel, close: () -> Unit) { val displayName = rememberSaveable { mutableStateOf("") } val focusRequester = remember { FocusRequester() } - Column( - modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()) + ColumnWithScrollBar( + modifier = Modifier.fillMaxSize() ) { Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { AppBarTitle(stringResource(MR.strings.create_profile), bottomPadding = DEFAULT_PADDING) @@ -120,8 +120,8 @@ fun CreateFirstProfile(chatModel: ChatModel, close: () -> Unit) { val displayName = rememberSaveable { mutableStateOf("") } val focusRequester = remember { FocusRequester() } - Column( - modifier = Modifier.fillMaxSize().verticalScroll(rememberScrollState()) + ColumnWithScrollBar( + modifier = Modifier.fillMaxSize() ) { /*CloseSheetBar(close = { if (chatModel.users.isEmpty()) { @@ -214,7 +214,8 @@ fun createProfileOnboarding(chatModel: ChatModel, displayName: String, close: () ) ?: return@withBGApi chatModel.localUserCreated.value = true val onboardingStage = chatModel.controller.appPrefs.onboardingStage - if (chatModel.users.isEmpty()) { + // No users or no visible users + if (chatModel.users.none { u -> !u.user.hidden }) { onboardingStage.set(if (appPlatform.isDesktop && chatModel.controller.appPrefs.initialRandomDBPassphrase.get() && !chatModel.desktopOnboardingRandomPassword.value) { OnboardingStage.Step2_5_SetupDatabasePassphrase } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt index b215badf2d..285658ec1d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/CallManager.kt @@ -1,6 +1,6 @@ package chat.simplex.common.views.call -import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.* import chat.simplex.common.platform.* import chat.simplex.common.views.helpers.withBGApi import kotlinx.datetime.Clock @@ -23,27 +23,29 @@ class CallManager(val chatModel: ChatModel) { } } - fun acceptIncomingCall(invitation: RcvCallInvitation) { + fun acceptIncomingCall(invitation: RcvCallInvitation) = withBGApi { val call = chatModel.activeCall.value - if (call == null) { - justAcceptIncomingCall(invitation = invitation) + val contactInfo = chatModel.controller.apiContactInfo(invitation.remoteHostId, invitation.contact.contactId) + val profile = contactInfo?.second ?: invitation.user.profile.toProfile() + // In case the same contact calling while previous call didn't end yet (abnormal ending of call from the other side) + if (call == null || (call.remoteHostId == invitation.remoteHostId && call.contact.id == invitation.contact.id)) { + justAcceptIncomingCall(invitation = invitation, profile) } else { - withBGApi { - chatModel.switchingCall.value = true - try { - endCall(call = call) - justAcceptIncomingCall(invitation = invitation) - } finally { - chatModel.switchingCall.value = false - } + chatModel.switchingCall.value = true + try { + endCall(call = call) + justAcceptIncomingCall(invitation = invitation, profile) + } finally { + chatModel.switchingCall.value = false } } } - private fun justAcceptIncomingCall(invitation: RcvCallInvitation) { + private fun justAcceptIncomingCall(invitation: RcvCallInvitation, userProfile: Profile) { with (chatModel) { activeCall.value = Call( remoteHostId = invitation.remoteHostId, + userProfile = userProfile, contact = invitation.contact, callState = CallState.InvitationAccepted, localMedia = invitation.callType.media, @@ -68,17 +70,23 @@ class CallManager(val chatModel: ChatModel) { } suspend fun endCall(call: Call) { - with (chatModel) { + with(chatModel) { + // If there is active call currently and it's with other contact, don't interrupt it + if (activeCall.value != null && !(activeCall.value?.remoteHostId == call.remoteHostId && activeCall.value?.contact?.id == call.contact.id)) return + + // Don't destroy WebView if you plan to accept next call right after this one + if (!switchingCall.value) { + showCallView.value = false + activeCall.value = null + activeCallViewIsCollapsed.value = false + platform.androidCallEnded() + } if (call.callState == CallState.Ended) { Log.d(TAG, "CallManager.endCall: call ended") - activeCall.value = null - showCallView.value = false } else { Log.d(TAG, "CallManager.endCall: ending call...") - callCommand.add(WCallCommand.End) - showCallView.value = false + //callCommand.add(WCallCommand.End) controller.apiEndCall(call.remoteHostId, call.contact) - activeCall.value = null } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt index 223a8a020f..754878e9fb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/call/WebRTC.kt @@ -7,11 +7,11 @@ import kotlinx.datetime.Instant import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import java.net.URI -import java.util.* import kotlin.collections.ArrayList data class Call( val remoteHostId: Long?, + val userProfile: Profile, val contact: Contact, val callState: CallState, val localMedia: CallMediaType, @@ -23,7 +23,7 @@ data class Call( val soundSpeaker: Boolean = localMedia == CallMediaType.Video, var localCamera: VideoCamera = VideoCamera.User, val connectionInfo: ConnectionInfo? = null, - var connectedAt: Instant? = null + var connectedAt: Instant? = null, ) { val encrypted: Boolean get() = localEncrypted && sharedKey != null val localEncrypted: Boolean get() = localCapabilities?.encryption ?: false @@ -36,6 +36,9 @@ data class Call( } val hasMedia: Boolean get() = callState == CallState.OfferSent || callState == CallState.Negotiated || callState == CallState.Connected + + fun supportsVideo(): Boolean = peerMedia == CallMediaType.Video || localMedia == CallMediaType.Video + } enum class CallState { @@ -75,6 +78,7 @@ sealed class WCallCommand { @Serializable @SerialName("media") data class Media(val media: CallMediaType, val enable: Boolean): WCallCommand() @Serializable @SerialName("camera") data class Camera(val camera: VideoCamera): WCallCommand() @Serializable @SerialName("description") data class Description(val state: String, val description: String): WCallCommand() + @Serializable @SerialName("layout") data class Layout(val layout: LayoutType): WCallCommand() @Serializable @SerialName("end") object End: WCallCommand() } @@ -167,6 +171,13 @@ enum class VideoCamera { val flipped: VideoCamera get() = if (this == User) Environment else User } +@Serializable +enum class LayoutType { + @SerialName("default") Default, + @SerialName("localVideo") LocalVideo, + @SerialName("remoteVideo") RemoteVideo +} + @Serializable data class ConnectionState( val connectionState: String, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt index f195c723f6..42257cf723 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatInfoView.kt @@ -58,6 +58,7 @@ fun ChatInfoView( val currentUser = remember { chatModel.currentUser }.value val connStats = remember(contact.id, connectionStats) { mutableStateOf(connectionStats) } val developerTools = chatModel.controller.appPrefs.developerTools.get() + val pqExperimentalEnabled = chatModel.controller.appPrefs.pqExperimentalEnabled.get() if (chat != null && currentUser != null) { val contactNetworkStatus = remember(chatModel.networkStatuses.toMap(), contact) { mutableStateOf(chatModel.contactNetworkStatus(contact)) @@ -80,6 +81,7 @@ fun ChatInfoView( localAlias, connectionCode, developerTools, + pqExperimentalEnabled, onLocalAliasChanged = { setContactAlias(chat, it, chatModel) }, @@ -138,6 +140,17 @@ fun ChatInfoView( } }) }, + allowContactPQ = { + showAllowContactPQAlert(allowContactPQ = { + withBGApi { + val ct = chatModel.controller.apiSetContactPQ(chatRh, contact.contactId, true) + if (ct != null) { + chatModel.updateContact(chatRh, ct) + } + close.invoke() + } + }) + }, verifyClicked = { ModalManager.end.showModalCloseable { close -> remember { derivedStateOf { (chatModel.getContactChat(contact.contactId)?.chatInfo as? ChatInfo.Direct)?.contact } }.value?.let { ct -> @@ -288,6 +301,7 @@ fun ChatInfoLayout( localAlias: String, connectionCode: String?, developerTools: Boolean, + pqExperimentalEnabled: Boolean, onLocalAliasChanged: (String) -> Unit, openPreferences: () -> Unit, deleteContact: () -> Unit, @@ -296,6 +310,7 @@ fun ChatInfoLayout( abortSwitchContactAddress: () -> Unit, syncContactConnection: () -> Unit, syncContactConnectionForce: () -> Unit, + allowContactPQ: () -> Unit, verifyClicked: () -> Unit, ) { val cStats = connStats.value @@ -304,10 +319,9 @@ fun ChatInfoLayout( KeyChangeEffect(chat.id) { scope.launch { scrollState.scrollTo(0) } } - Column( + ColumnWithScrollBar( Modifier .fillMaxWidth() - .verticalScroll(scrollState) ) { Row( Modifier.fillMaxWidth(), @@ -345,6 +359,18 @@ fun ChatInfoLayout( SectionDividerSpaced() } + val conn = contact.activeConn + if (pqExperimentalEnabled && conn != null) { + SectionView("Quantum resistant E2E encryption") { + InfoRow("E2E encryption", if (conn.connPQEnabled) "Quantum resistant" else "Standard") + if (!conn.pqEncryption) { + AllowContactPQButton(allowContactPQ) + SectionTextFooter("After allowing quantum resistant e2e encryption, it will be enabled after several messages if your contact also allows it.") + } + SectionDividerSpaced() + } + } + if (contact.contactLink != null) { SectionView(stringResource(MR.strings.address_section_title).uppercase()) { SimpleXLinkQRCode(contact.contactLink) @@ -601,6 +627,17 @@ fun SynchronizeConnectionButtonForce(syncConnectionForce: () -> Unit) { ) } +@Composable +fun AllowContactPQButton(allowContactPQ: () -> Unit) { + SettingsActionItem( + painterResource(MR.images.ic_warning), + "Allow PQ encryption", + click = allowContactPQ, + textColor = WarningOrange, + iconColor = WarningOrange + ) +} + @Composable fun VerifyCodeButton(contactVerified: Boolean, onClick: () -> Unit) { SettingsActionItem( @@ -704,6 +741,16 @@ fun showSyncConnectionForceAlert(syncConnectionForce: () -> Unit) { ) } +fun showAllowContactPQAlert(allowContactPQ: () -> Unit) { + AlertManager.shared.showAlertDialog( + title = "Allow quantum resistant encryption?", + text = "This is an experimental feature, it is not recommended to enable it for important chats.", + confirmText = "Allow", + onConfirm = allowContactPQ, + destructive = true, + ) +} + @Preview @Composable fun PreviewChatInfoLayout() { @@ -721,6 +768,7 @@ fun PreviewChatInfoLayout() { localAlias = "", connectionCode = "123", developerTools = false, + pqExperimentalEnabled = false, connStats = remember { mutableStateOf(null) }, contactNetworkStatus = NetworkStatus.Connected(), onLocalAliasChanged = {}, @@ -732,6 +780,7 @@ fun PreviewChatInfoLayout() { abortSwitchContactAddress = {}, syncContactConnection = {}, syncContactConnectionForce = {}, + allowContactPQ = {}, verifyClicked = {}, ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt index 4347623bd1..14bb3543c3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatItemInfoView.kt @@ -24,11 +24,10 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* -import chat.simplex.common.platform.onRightClick +import chat.simplex.common.platform.* import chat.simplex.common.views.chat.item.ItemAction import chat.simplex.common.views.chat.item.MarkdownText import chat.simplex.common.views.helpers.* -import chat.simplex.common.platform.shareText import chat.simplex.common.ui.theme.* import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource @@ -186,7 +185,8 @@ fun ChatItemInfoView(chatModel: ChatModel, ci: ChatItem, ciInfo: ChatItemInfo, d @Composable fun HistoryTab() { - Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) { + // LALAL SCROLLBAR DOESN'T WORK + ColumnWithScrollBar(Modifier.fillMaxWidth()) { Details() SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false) val versions = ciInfo.itemVersions @@ -210,7 +210,8 @@ fun ChatItemInfoView(chatModel: ChatModel, ci: ChatItem, ciInfo: ChatItemInfo, d @Composable fun QuoteTab(qi: CIQuote) { - Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) { + // LALAL SCROLLBAR DOESN'T WORK + ColumnWithScrollBar(Modifier.fillMaxWidth()) { Details() SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false) SectionView(padding = PaddingValues(horizontal = DEFAULT_PADDING)) { @@ -267,7 +268,8 @@ fun ChatItemInfoView(chatModel: ChatModel, ci: ChatItem, ciInfo: ChatItemInfo, d @Composable fun DeliveryTab(memberDeliveryStatuses: List<MemberDeliveryStatus>) { - Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) { + // LALAL SCROLLBAR DOESN'T WORK + ColumnWithScrollBar(Modifier.fillMaxWidth()) { Details() SectionDividerSpaced(maxTopPadding = false, maxBottomPadding = false) val mss = membersStatuses(chatModel, memberDeliveryStatuses) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt index 9a92997f8e..c70511f847 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ChatView.kt @@ -1,5 +1,6 @@ package chat.simplex.common.views.chat +import androidx.compose.animation.core.Animatable import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.* import androidx.compose.foundation.gestures.* @@ -21,6 +22,7 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.input.pointer.* import androidx.compose.ui.text.* import androidx.compose.ui.unit.* import chat.simplex.common.model.* @@ -301,7 +303,9 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: withBGApi { val cInfo = chat.chatInfo if (cInfo is ChatInfo.Direct) { - chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media) + val contactInfo = chatModel.controller.apiContactInfo(chat.remoteHostId, cInfo.contact.contactId) + val profile = contactInfo?.second ?: chatModel.currentUser.value?.profile?.toProfile() ?: return@withBGApi + chatModel.activeCall.value = Call(remoteHostId = chatRh, contact = cInfo.contact, callState = CallState.WaitCapabilities, localMedia = media, userProfile = profile) chatModel.showCallView.value = true chatModel.callCommand.add(WCallCommand.Capabilities(media)) } @@ -471,7 +475,7 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: } is ChatInfo.InvalidJSON -> { val close = { chatModel.chatId.value = null } - ModalView(close, showClose = appPlatform.isAndroid, content = { + ModalView(close, showClose = appPlatform.isAndroid, endButtons = { ShareButton { clipboard.shareText(chat.chatInfo.json) } }, content = { InvalidJSONView(chat.chatInfo.json) }) LaunchedEffect(chat.id) { @@ -673,7 +677,7 @@ fun ChatInfoToolbar( } } } - } else if (activeCall?.contact?.id == chat.id) { + } else if (activeCall?.contact?.id == chat.id && appPlatform.isDesktop) { barButtons.add { val call = remember { chatModel.activeCall }.value val connectedAt = call?.connectedAt @@ -905,7 +909,7 @@ fun BoxWithConstraintsScope.ChatItemsList( VideoPlayerHolder.releaseAll() } ) - LazyColumn(Modifier.align(Alignment.BottomCenter), state = listState, reverseLayout = true) { + LazyColumnWithScrollBar(Modifier.align(Alignment.BottomCenter), state = listState, reverseLayout = true) { itemsIndexed(reversedChatItems, key = { _, item -> item.id }) { i, cItem -> CompositionLocalProvider( // Makes horizontal and vertical scrolling to coexist nicely. diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt index f412c02154..5ad98aa216 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContactPreferences.kt @@ -19,6 +19,7 @@ import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.PreferenceToggle import chat.simplex.common.model.* +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.res.MR @Composable @@ -77,10 +78,9 @@ private fun ContactPreferencesLayout( reset: () -> Unit, savePrefs: () -> Unit, ) { - Column( + ColumnWithScrollBar( Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()), + .fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.contact_preferences)) val timedMessages: MutableState<Boolean> = remember(featuresAllowed) { mutableStateOf(featuresAllowed.timedMessagesAllowed) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt index 57c469adf2..5bd707ab66 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/VerifyCodeView.kt @@ -16,8 +16,7 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.common.platform.appPlatform -import chat.simplex.common.platform.shareText +import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.newchat.QRCode @@ -57,10 +56,9 @@ private fun VerifyCodeLayout( connectionVerified: Boolean, verifyCode: (String?, cb: (Boolean) -> Unit) -> Unit, ) { - Column( + ColumnWithScrollBar( Modifier .fillMaxSize() - .verticalScroll(rememberScrollState()) .padding(horizontal = DEFAULT_PADDING) ) { AppBarTitle(stringResource(MR.strings.security_code), withPadding = false) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt index 3f283f704e..d546b51a93 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/AddGroupMembersView.kt @@ -125,10 +125,9 @@ fun AddGroupMembersLayout( } } - Column( + ColumnWithScrollBar( Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()), + .fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.button_add_members)) profileText() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt index 215e7a880f..bfd7b5d52f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt @@ -205,7 +205,8 @@ fun GroupChatInfoLayout( } val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue()) } val filteredMembers = remember(members) { derivedStateOf { members.filter { it.chatViewName.lowercase().contains(searchText.value.text.trim()) } } } - LazyColumn( + // LALAL strange scrolling + LazyColumnWithScrollBar( Modifier .fillMaxWidth(), state = listState @@ -424,69 +425,47 @@ private fun MemberVerifiedShield() { @Composable private fun DropDownMenuForMember(rhId: Long?, member: GroupMember, groupInfo: GroupInfo, showMenu: MutableState<Boolean>) { - // revert from this: - DefaultDropdownMenu(showMenu) { - if (member.canBeRemoved(groupInfo)) { - ItemAction(stringResource(MR.strings.remove_member_button), painterResource(MR.images.ic_delete), color = MaterialTheme.colors.error, onClick = { - removeMemberAlert(rhId, groupInfo, member) - showMenu.value = false - }) + if (groupInfo.membership.memberRole >= GroupMemberRole.Admin) { + val canBlockForAll = member.canBlockForAll(groupInfo) + val canRemove = member.canBeRemoved(groupInfo) + if (canBlockForAll || canRemove) { + DefaultDropdownMenu(showMenu) { + if (canBlockForAll) { + if (member.blockedByAdmin) { + ItemAction(stringResource(MR.strings.unblock_for_all), painterResource(MR.images.ic_do_not_touch), onClick = { + unblockForAllAlert(rhId, groupInfo, member) + showMenu.value = false + }) + } else { + ItemAction(stringResource(MR.strings.block_for_all), painterResource(MR.images.ic_back_hand), color = MaterialTheme.colors.error, onClick = { + blockForAllAlert(rhId, groupInfo, member) + showMenu.value = false + }) + } + } + if (canRemove) { + ItemAction(stringResource(MR.strings.remove_member_button), painterResource(MR.images.ic_delete), color = MaterialTheme.colors.error, onClick = { + removeMemberAlert(rhId, groupInfo, member) + showMenu.value = false + }) + } + } } - if (member.memberSettings.showMessages) { - ItemAction(stringResource(MR.strings.block_member_button), painterResource(MR.images.ic_back_hand), color = MaterialTheme.colors.error, onClick = { - blockMemberAlert(rhId, groupInfo, member) - showMenu.value = false - }) - } else { - ItemAction(stringResource(MR.strings.unblock_member_button), painterResource(MR.images.ic_do_not_touch), onClick = { - unblockMemberAlert(rhId, groupInfo, member) - showMenu.value = false - }) + } else if (!member.blockedByAdmin) { + DefaultDropdownMenu(showMenu) { + if (member.memberSettings.showMessages) { + ItemAction(stringResource(MR.strings.block_member_button), painterResource(MR.images.ic_back_hand), color = MaterialTheme.colors.error, onClick = { + blockMemberAlert(rhId, groupInfo, member) + showMenu.value = false + }) + } else { + ItemAction(stringResource(MR.strings.unblock_member_button), painterResource(MR.images.ic_do_not_touch), onClick = { + unblockMemberAlert(rhId, groupInfo, member) + showMenu.value = false + }) + } } } - // revert to this: vvv -// if (groupInfo.membership.memberRole >= GroupMemberRole.Admin) { -// val canBlockForAll = member.canBlockForAll(groupInfo) -// val canRemove = member.canBeRemoved(groupInfo) -// if (canBlockForAll || canRemove) { -// DefaultDropdownMenu(showMenu) { -// if (canBlockForAll) { -// if (member.blockedByAdmin) { -// ItemAction(stringResource(MR.strings.unblock_for_all), painterResource(MR.images.ic_do_not_touch), onClick = { -// unblockForAllAlert(rhId, groupInfo, member) -// showMenu.value = false -// }) -// } else { -// ItemAction(stringResource(MR.strings.block_for_all), painterResource(MR.images.ic_back_hand), color = MaterialTheme.colors.error, onClick = { -// blockForAllAlert(rhId, groupInfo, member) -// showMenu.value = false -// }) -// } -// } -// if (canRemove) { -// ItemAction(stringResource(MR.strings.remove_member_button), painterResource(MR.images.ic_delete), color = MaterialTheme.colors.error, onClick = { -// removeMemberAlert(rhId, groupInfo, member) -// showMenu.value = false -// }) -// } -// } -// } -// } else if (!member.blockedByAdmin) { -// DefaultDropdownMenu(showMenu) { -// if (member.memberSettings.showMessages) { -// ItemAction(stringResource(MR.strings.block_member_button), painterResource(MR.images.ic_back_hand), color = MaterialTheme.colors.error, onClick = { -// blockMemberAlert(rhId, groupInfo, member) -// showMenu.value = false -// }) -// } else { -// ItemAction(stringResource(MR.strings.unblock_member_button), painterResource(MR.images.ic_do_not_touch), onClick = { -// unblockMemberAlert(rhId, groupInfo, member) -// showMenu.value = false -// }) -// } -// } -// } - // ^^^ } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt index 7b198bc41a..5291520566 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupLinkView.kt @@ -16,6 +16,7 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.platform.shareText import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* @@ -118,9 +119,8 @@ fun GroupLinkLayout( ) } - Column( - Modifier - .verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier, ) { AppBarTitle(stringResource(MR.strings.group_link)) Text( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt index 6759d54749..ebb35a4468 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupMemberInfoView.kt @@ -291,10 +291,9 @@ fun GroupMemberInfoLayout( } } - Column( + ColumnWithScrollBar( Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()), + .fillMaxWidth(), ) { Row( Modifier.fillMaxWidth(), @@ -387,25 +386,11 @@ fun GroupMemberInfoLayout( } } - // revert from this: - SectionDividerSpaced(maxBottomPadding = false) - SectionView { - if (member.memberSettings.showMessages) { - BlockMemberButton(blockMember) - } else { - UnblockMemberButton(unblockMember) - } - if (member.canBeRemoved(groupInfo)) { - RemoveMemberButton(removeMember) - } + if (groupInfo.membership.memberRole >= GroupMemberRole.Admin) { + AdminDestructiveSection() + } else { + NonAdminBlockSection() } - // revert to this: vvv -// if (groupInfo.membership.memberRole >= GroupMemberRole.Admin) { -// AdminDestructiveSection() -// } else { -// NonAdminBlockSection() -// } - // ^^^ if (developerTools) { SectionDividerSpaced() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt index 694dac8898..607efb9a3b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupPreferences.kt @@ -18,6 +18,7 @@ import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.PreferenceToggleWithIcon import chat.simplex.common.model.* +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.res.MR @Composable @@ -72,8 +73,8 @@ private fun GroupPreferencesLayout( reset: () -> Unit, savePrefs: () -> Unit, ) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.group_preferences)) val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.enable) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt index 35bec31d7b..7975a298d1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupProfileView.kt @@ -95,9 +95,8 @@ fun GroupProfileLayout( sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) ) { ModalView(close = closeWithAlert) { - Column( + ColumnWithScrollBar( Modifier - .verticalScroll(scrollState) ) { Column( Modifier.fillMaxWidth() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt index 6c2c37503c..3bbeceb03c 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/WelcomeMessageView.kt @@ -29,6 +29,7 @@ import chat.simplex.common.views.chat.item.MarkdownText import chat.simplex.common.views.helpers.* import chat.simplex.common.model.ChatModel import chat.simplex.common.model.GroupInfo +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.platform.chatJsonLength import chat.simplex.common.ui.theme.DEFAULT_PADDING_HALF import chat.simplex.res.MR @@ -91,8 +92,8 @@ private fun GroupWelcomeLayout( linkMode: SimplexLinkMode, save: () -> Unit, ) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), ) { val editMode = remember { mutableStateOf(true) } AppBarTitle(stringResource(MR.strings.group_welcome_title)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIInvalidJSONView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIInvalidJSONView.kt index 21c27fa34e..d297d5ef76 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIInvalidJSONView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIInvalidJSONView.kt @@ -1,6 +1,5 @@ package chat.simplex.common.views.chat.item -import SectionView import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* @@ -8,23 +7,23 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.platform.LocalClipboardManager -import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.unit.dp +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.platform.shareText import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.usersettings.SettingsActionItem import chat.simplex.res.MR @Composable fun CIInvalidJSONView(json: String) { + val clipboard = LocalClipboardManager.current Row(Modifier .clickable { ModalManager.center.closeModals() ModalManager.end.closeModals() - ModalManager.center.showModal(true) { InvalidJSONView(json) } + ModalManager.center.showModal(true, endButtons = { ShareButton { clipboard.shareText(json) } }) { InvalidJSONView(json) } } .padding(horizontal = 10.dp, vertical = 6.dp) ) { @@ -34,15 +33,8 @@ fun CIInvalidJSONView(json: String) { @Composable fun InvalidJSONView(json: String) { - Column { - Spacer(Modifier.height(DEFAULT_PADDING)) - SectionView { - val clipboard = LocalClipboardManager.current - SettingsActionItem(painterResource(MR.images.ic_share), generalGetString(MR.strings.share_verb), click = { - clipboard.shareText(json) - }) - } - Column(Modifier.padding(DEFAULT_PADDING).fillMaxWidth().verticalScroll(rememberScrollState())) { + ColumnWithScrollBar { + Column(Modifier.padding(DEFAULT_PADDING).fillMaxWidth()) { Text(json) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt index ccb9683240..64741f7466 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/ChatItemView.kt @@ -379,6 +379,30 @@ fun ChatItemView( } } + @Composable + fun E2EEInfoNoPQText() { + Text( + buildAnnotatedString { + withStyle(chatEventStyle) { append(annotatedStringResource(MR.strings.e2ee_info_no_pq)) } + }, + Modifier.padding(horizontal = 6.dp, vertical = 6.dp) + ) + } + + @Composable + fun DirectE2EEInfoText(e2EEInfo: E2EEInfo) { + if (e2EEInfo.pqEnabled) { + Text( + buildAnnotatedString { + withStyle(chatEventStyle) { append(annotatedStringResource(MR.strings.e2ee_info_pq)) } + }, + Modifier.padding(horizontal = 6.dp, vertical = 6.dp) + ) + } else { + E2EEInfoNoPQText() + } + } + when (val c = cItem.content) { is CIContent.SndMsgContent -> ContentItem() is CIContent.RcvMsgContent -> ContentItem() @@ -452,6 +476,10 @@ fun ChatItemView( is CIContent.SndModerated -> DeletedItem() is CIContent.RcvModerated -> DeletedItem() is CIContent.RcvBlocked -> DeletedItem() + is CIContent.SndDirectE2EEInfo -> DirectE2EEInfoText(c.e2eeInfo) + is CIContent.RcvDirectE2EEInfo -> DirectE2EEInfoText(c.e2eeInfo) + is CIContent.SndGroupE2EEInfo -> E2EEInfoNoPQText() + is CIContent.RcvGroupE2EEInfo -> E2EEInfoNoPQText() is CIContent.InvalidJSON -> CIInvalidJSONView(c.json) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 3a47d062ac..d03b8a708d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -29,6 +29,7 @@ import chat.simplex.common.views.onboarding.WhatsNewView import chat.simplex.common.views.onboarding.shouldShowWhatsNew import chat.simplex.common.views.usersettings.SettingsView import chat.simplex.common.platform.* +import chat.simplex.common.views.call.Call import chat.simplex.common.views.newchat.* import chat.simplex.res.MR import kotlinx.coroutines.* @@ -121,7 +122,12 @@ fun ChatListView(chatModel: ChatModel, settingsState: SettingsViewState, setPerf } } if (searchText.value.text.isEmpty()) { - DesktopActiveCallOverlayLayout(newChatSheetState) + if (appPlatform.isDesktop) { + val call = remember { chatModel.activeCall }.value + if (call != null) { + ActiveCallInteractiveArea(call, newChatSheetState) + } + } // TODO disable this button and sheet for the duration of the switch tryOrShowError("NewChatSheet", error = {}) { NewChatSheet(chatModel, newChatSheetState, stopped, hideNewChatSheet) @@ -314,7 +320,7 @@ private fun ToggleFilterDisabledButton() { } @Composable -expect fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow<AnimatedViewState>) +expect fun ActiveCallInteractiveArea(call: Call, newChatSheetState: MutableStateFlow<AnimatedViewState>) fun connectIfOpenedViaUri(rhId: Long?, uri: URI, chatModel: ChatModel) { Log.d(TAG, "connectIfOpenedViaUri: opened via link") @@ -454,7 +460,7 @@ private fun ChatList(chatModel: ChatModel, searchText: MutableState<TextFieldVal val searchShowingSimplexLink = remember { mutableStateOf(false) } val searchChatFilteredBySimplexLink = remember { mutableStateOf<String?>(null) } val chats = filteredChats(showUnreadAndFavorites, searchShowingSimplexLink, searchChatFilteredBySimplexLink, searchText.value.text, allChats.toList()) - LazyColumn( + LazyColumnWithScrollBar( Modifier.fillMaxWidth(), listState ) { @@ -529,7 +535,9 @@ private fun filteredChats( } private fun filtered(chat: Chat): Boolean = - (chat.chatInfo.chatSettings?.favorite ?: false) || chat.chatStats.unreadCount > 0 || chat.chatStats.unreadChat + (chat.chatInfo.chatSettings?.favorite ?: false) || + chat.chatStats.unreadChat || + (chat.chatInfo.ntfsEnabled && chat.chatStats.unreadCount > 0) private fun viewNameContains(cInfo: ChatInfo, s: String): Boolean = cInfo.chatViewName.lowercase().contains(s.lowercase()) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt index b1e87823c8..04fef25ac2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ShareListView.kt @@ -1,7 +1,6 @@ package chat.simplex.common.views.chatlist import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.* import androidx.compose.runtime.* @@ -14,12 +13,10 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.dp import chat.simplex.common.SettingsViewState -import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.model.Chat import chat.simplex.common.model.ChatModel -import chat.simplex.common.platform.BackHandler -import chat.simplex.common.platform.appPlatform +import chat.simplex.common.platform.* import chat.simplex.res.MR import kotlinx.coroutines.flow.MutableStateFlow @@ -85,7 +82,7 @@ private fun ShareListToolbar(chatModel: ChatModel, userPickerState: MutableState userPickerState.value = AnimatedViewState.VISIBLE } } - else -> NavigationButtonBack { chatModel.sharedContent.value = null } + else -> NavigationButtonBack(onButtonClicked = { chatModel.sharedContent.value = null }) } } if (chatModel.chats.size >= 8) { @@ -143,10 +140,10 @@ private fun ShareList(chatModel: ChatModel, search: String) { } val chats by remember(search) { derivedStateOf { - if (search.isEmpty()) chatModel.chats.filter { it.chatInfo.ready } else chatModel.chats.filter { it.chatInfo.ready }.filter(filter) + if (search.isEmpty()) chatModel.chats.toList().filter { it.chatInfo.ready } else chatModel.chats.toList().filter { it.chatInfo.ready }.filter(filter) } } - LazyColumn( + LazyColumnWithScrollBar( modifier = Modifier.fillMaxWidth() ) { items(chats) { chat -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt index 1e2f8420fd..6846d1c735 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/ChatArchiveView.kt @@ -46,7 +46,7 @@ fun ChatArchiveLayout( saveArchive: () -> Unit, deleteArchiveAlert: () -> Unit ) { - Column( + ColumnWithScrollBar( Modifier.fillMaxWidth(), ) { AppBarTitle(title) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt index 7bd9fbc66f..ca14b19adf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.kt @@ -1,9 +1,8 @@ package chat.simplex.common.views.database import SectionBottomSpacer -import SectionItemView import SectionItemViewSpaceBetween -import SectionTextFooter +import SectionSpacer import SectionView import androidx.compose.foundation.* import androidx.compose.foundation.interaction.MutableInteractionSource @@ -24,20 +23,22 @@ import androidx.compose.ui.text.* import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.input.* import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester import androidx.compose.ui.unit.* import chat.simplex.common.model.* +import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* -import chat.simplex.common.model.* -import chat.simplex.common.platform.appPlatform +import chat.simplex.common.views.usersettings.SettingsActionItem import chat.simplex.res.MR +import kotlinx.coroutines.delay import kotlinx.coroutines.flow.distinctUntilChanged import kotlinx.datetime.Clock import kotlin.math.log2 @Composable -fun DatabaseEncryptionView(m: ChatModel) { +fun DatabaseEncryptionView(m: ChatModel, migration: Boolean) { val progressIndicator = remember { mutableStateOf(false) } val prefs = m.controller.appPrefs val useKeychain = remember { mutableStateOf(prefs.storeDBPassphrase.get()) } @@ -61,9 +62,10 @@ fun DatabaseEncryptionView(m: ChatModel) { storedKey, initialRandomDBPassphrase, progressIndicator, + migration, onConfirmEncrypt = { withLongRunningApi { - encryptDatabase(currentKey, newKey, confirmNewKey, initialRandomDBPassphrase, useKeychain, storedKey, progressIndicator) + encryptDatabase(currentKey, newKey, confirmNewKey, initialRandomDBPassphrase, useKeychain, storedKey, progressIndicator, migration) } } ) @@ -95,24 +97,36 @@ fun DatabaseEncryptionLayout( storedKey: MutableState<Boolean>, initialRandomDBPassphrase: MutableState<Boolean>, progressIndicator: MutableState<Boolean>, + migration: Boolean, onConfirmEncrypt: () -> Unit, ) { + val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents() + val scrollState = rememberScrollState() Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + if (!migration) Modifier.fillMaxWidth().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier) else Modifier.fillMaxWidth(), ) { - AppBarTitle(stringResource(MR.strings.database_passphrase)) - SectionView(null) { - SavePassphraseSetting(useKeychain.value, initialRandomDBPassphrase.value, storedKey.value, progressIndicator.value) { checked -> + if (!migration) { + AppBarTitle(stringResource(MR.strings.database_passphrase)) + } else { + ChatStoppedView() + SectionSpacer() + } + SectionView(if (migration) generalGetString(MR.strings.database_passphrase).uppercase() else null) { + SavePassphraseSetting( + useKeychain.value, + initialRandomDBPassphrase.value, + storedKey.value, + enabled = (!initialRandomDBPassphrase.value && !progressIndicator.value) || migration + ) { checked -> if (checked) { - setUseKeychain(true, useKeychain, prefs) - } else if (storedKey.value) { + setUseKeychain(true, useKeychain, prefs, migration) + } else if (storedKey.value && !migration) { + // Don't show in migration process since it will remove the key after successful encryption removePassphraseAlert { - DatabaseUtils.ksDatabasePassword.remove() - setUseKeychain(false, useKeychain, prefs) - storedKey.value = false + removePassphraseFromKeyChain(useKeychain, prefs, storedKey, false) } } else { - setUseKeychain(false, useKeychain, prefs) + setUseKeychain(false, useKeychain, prefs, migration) } } @@ -169,15 +183,20 @@ fun DatabaseEncryptionLayout( ) SectionItemViewSpaceBetween(onClickUpdate, disabled = disabled, minHeight = TextFieldDefaults.MinHeight) { - Text(generalGetString(MR.strings.update_database_passphrase), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) + Text(generalGetString(if (migration) MR.strings.set_passphrase else MR.strings.update_database_passphrase), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.primary) } } Column { - DatabaseEncryptionFooter(useKeychain, chatDbEncrypted, storedKey, initialRandomDBPassphrase) + DatabaseEncryptionFooter(useKeychain, chatDbEncrypted, storedKey, initialRandomDBPassphrase, migration) } SectionBottomSpacer() } + if (appPlatform.isDesktop && !migration) { + Box(Modifier.fillMaxSize()) { + platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false) + } + } } expect fun encryptDatabaseSavedAlert(onConfirm: () -> Unit) @@ -211,8 +230,9 @@ expect fun SavePassphraseSetting( useKeychain: Boolean, initialRandomDBPassphrase: Boolean, storedKey: Boolean, - progressIndicator: Boolean, minHeight: Dp = TextFieldDefaults.MinHeight, + enabled: Boolean, + smallPadding: Boolean = true, onCheckedChange: (Boolean) -> Unit, ) @@ -222,8 +242,18 @@ expect fun DatabaseEncryptionFooter( chatDbEncrypted: Boolean?, storedKey: MutableState<Boolean>, initialRandomDBPassphrase: MutableState<Boolean>, + migration: Boolean, ) +@Composable +fun ChatStoppedView() { + SettingsActionItem( + icon = painterResource(MR.images.ic_report_filled), + text = stringResource(MR.strings.chat_is_stopped), + iconColor = Color.Red, + ) +} + fun resetFormAfterEncryption( m: ChatModel, initialRandomDBPassphrase: MutableState<Boolean>, @@ -242,9 +272,18 @@ fun resetFormAfterEncryption( m.controller.appPrefs.initialRandomDBPassphrase.set(false) } -fun setUseKeychain(value: Boolean, useKeychain: MutableState<Boolean>, prefs: AppPreferences) { +fun setUseKeychain(value: Boolean, useKeychain: MutableState<Boolean>, prefs: AppPreferences, migration: Boolean) { useKeychain.value = value - prefs.storeDBPassphrase.set(value) + // Postpone it when migrating to the end of encryption process + if (!migration) { + prefs.storeDBPassphrase.set(value) + } +} + +private fun removePassphraseFromKeyChain(useKeychain: MutableState<Boolean>, prefs: AppPreferences, storedKey: MutableState<Boolean>, migration: Boolean) { + DatabaseUtils.ksDatabasePassword.remove() + setUseKeychain(false, useKeychain, prefs, migration) + storedKey.value = false } fun storeSecurelySaved() = generalGetString(MR.strings.store_passphrase_securely) @@ -267,6 +306,7 @@ fun PassphraseField( isValid: (String) -> Boolean, keyboardActions: KeyboardActions = KeyboardActions(), dependsOn: State<Any?>? = null, + requestFocus: Boolean = false, ) { var valid by remember { mutableStateOf(validKey(key.value)) } var showKey by remember { mutableStateOf(false) } @@ -295,6 +335,7 @@ fun PassphraseField( val color = MaterialTheme.colors.onBackground val shape = MaterialTheme.shapes.small.copy(bottomEnd = ZeroCornerSize, bottomStart = ZeroCornerSize) val interactionSource = remember { MutableInteractionSource() } + val focusRequester = remember { FocusRequester() } BasicTextField( value = state.value, modifier = modifier @@ -304,7 +345,8 @@ fun PassphraseField( .defaultMinSize( minWidth = TextFieldDefaults.MinWidth, minHeight = TextFieldDefaults.MinHeight - ), + ) + .focusRequester(focusRequester), onValueChange = { state.value = it key.value = it.text @@ -347,6 +389,12 @@ fun PassphraseField( ) } ) + LaunchedEffect(Unit) { + if (requestFocus) { + delay(200) + focusRequester.requestFocus() + } + } LaunchedEffect(Unit) { snapshotFlow { dependsOn?.value } .distinctUntilChanged() @@ -363,13 +411,17 @@ suspend fun encryptDatabase( initialRandomDBPassphrase: MutableState<Boolean>, useKeychain: MutableState<Boolean>, storedKey: MutableState<Boolean>, - progressIndicator: MutableState<Boolean> + progressIndicator: MutableState<Boolean>, + migration: Boolean, ): Boolean { val m = ChatModel val prefs = ChatController.appPrefs progressIndicator.value = true return try { prefs.encryptionStartedAt.set(Clock.System.now()) + if (!m.chatDbChanged.value) { + m.controller.apiSaveAppSettings(AppSettings.current.prepareForExport()) + } val error = m.controller.apiStorageEncryption(currentKey.value, newKey.value) prefs.encryptionStartedAt.set(null) val sqliteError = ((error?.chatError as? ChatError.ChatErrorDatabase)?.databaseError as? DatabaseError.ErrorExport)?.sqliteError @@ -393,9 +445,14 @@ suspend fun encryptDatabase( } else -> { val new = newKey.value + if (migration) { + appPreferences.storeDBPassphrase.set(useKeychain.value) + } resetFormAfterEncryption(m, initialRandomDBPassphrase, currentKey, newKey, confirmNewKey, storedKey, useKeychain.value) if (useKeychain.value) { DatabaseUtils.ksDatabasePassword.set(new) + } else if (migration) { + removePassphraseFromKeyChain(useKeychain, prefs, storedKey, true) } operationEnded(m, progressIndicator) { AlertManager.shared.showAlertMsg(generalGetString(MR.strings.database_encrypted)) @@ -474,6 +531,7 @@ fun PreviewDatabaseEncryptionLayout() { storedKey = remember { mutableStateOf(true) }, initialRandomDBPassphrase = remember { mutableStateOf(true) }, progressIndicator = remember { mutableStateOf(false) }, + migration = false, onConfirmEncrypt = {}, ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt index 0c208c06e8..5f0356bb2d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseErrorView.kt @@ -76,8 +76,8 @@ fun DatabaseErrorView( Text(String.format(generalGetString(MR.strings.database_migrations), ms.joinToString(", "))) } - Column( - Modifier.fillMaxSize().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxSize(), verticalArrangement = Arrangement.Center, ) { val buttonEnabled = validKey(dbKey.value) && !progressIndicator.value @@ -206,6 +206,14 @@ private fun runChat( is DBMigrationResult.OK -> { platform.androidChatStartedAfterBeingOff() } + null -> {} + else -> showErrorOnMigrationIfNeeded(status) + } +} + +fun showErrorOnMigrationIfNeeded(status: DBMigrationResult) = + when (status) { + is DBMigrationResult.OK -> {} is DBMigrationResult.ErrorNotADatabase -> AlertManager.shared.showAlertMsg(generalGetString(MR.strings.wrong_passphrase_title), generalGetString(MR.strings.enter_correct_passphrase)) is DBMigrationResult.ErrorSQL -> @@ -217,9 +225,7 @@ private fun runChat( is DBMigrationResult.InvalidConfirmation -> AlertManager.shared.showAlertMsg(generalGetString(MR.strings.invalid_migration_confirmation)) is DBMigrationResult.ErrorMigration -> {} - null -> {} } -} private fun shouldShowRestoreDbButton(prefs: AppPreferences): Boolean { val startedAt = prefs.encryptionStartedAt.get() ?: return false @@ -246,7 +252,7 @@ private fun restoreDb(restoreDbFromBackup: MutableState<Boolean>, prefs: AppPref } } -private fun mtrErrorDescription(err: MTRError): String = +fun mtrErrorDescription(err: MTRError): String = when (err) { is MTRError.NoDown -> String.format(generalGetString(MR.strings.mtr_error_no_down_migration), err.dbMigrations.joinToString(", ")) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt index 8680c98d46..814ff9969a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/database/DatabaseView.kt @@ -7,8 +7,6 @@ import SectionItemView import SectionView import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -27,7 +25,6 @@ import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.* import chat.simplex.common.platform.* import chat.simplex.res.MR -import kotlinx.coroutines.delay import kotlinx.coroutines.sync.withLock import kotlinx.datetime.* import java.io.* @@ -158,8 +155,8 @@ fun DatabaseLayout( val stopped = !runChat val operationsDisabled = (!stopped || progressIndicator) && !chatModel.desktopNoUserNoRemote - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.your_chat_database)) @@ -211,7 +208,7 @@ fun DatabaseLayout( if (unencrypted) painterResource(MR.images.ic_lock_open_right) else if (useKeyChain) painterResource(MR.images.ic_vpn_key_filled) else painterResource(MR.images.ic_lock), stringResource(MR.strings.database_passphrase), - click = showSettingsModal() { DatabaseEncryptionView(it) }, + click = showSettingsModal() { DatabaseEncryptionView(it, false) }, iconColor = if (unencrypted || (appPlatform.isDesktop && passphraseSaved)) WarningOrange else MaterialTheme.colors.secondary, disabled = operationsDisabled ) @@ -391,7 +388,7 @@ fun startChat(m: ChatModel, chatLastStart: MutableState<Instant?>, chatDbChanged m.controller.appPrefs.chatLastStart.set(ts) chatLastStart.value = ts platform.androidChatStartedAfterBeingOff() - } catch (e: Error) { + } catch (e: Throwable) { m.chatRunning.value = false AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_starting_chat), e.toString()) } finally { @@ -486,6 +483,7 @@ fun deleteChatDatabaseFilesAndState() { filesDir.mkdir() remoteHostsDir.deleteRecursively() tmpDir.deleteRecursively() + getMigrationTempFilesDirectory().deleteRecursively() tmpDir.mkdir() DatabaseUtils.ksDatabasePassword.remove() controller.appPrefs.storeDBPassphrase.set(true) @@ -509,7 +507,7 @@ private fun exportArchive( progressIndicator.value = true withLongRunningApi { try { - val archiveFile = exportChatArchive(m, chatArchiveName, chatArchiveTime, chatArchiveFile) + val archiveFile = exportChatArchive(m, null, chatArchiveName, chatArchiveTime, chatArchiveFile) chatArchiveFile.value = archiveFile saveArchiveLauncher.launch(archiveFile.substringAfterLast(File.separator)) progressIndicator.value = false @@ -520,8 +518,9 @@ private fun exportArchive( } } -private suspend fun exportChatArchive( +suspend fun exportChatArchive( m: ChatModel, + storagePath: File?, chatArchiveName: MutableState<String?>, chatArchiveTime: MutableState<Instant?>, chatArchiveFile: MutableState<String?> @@ -529,13 +528,19 @@ private suspend fun exportChatArchive( val archiveTime = Clock.System.now() val ts = SimpleDateFormat("yyyy-MM-dd'T'HHmmss", Locale.US).format(Date.from(archiveTime.toJavaInstant())) val archiveName = "simplex-chat.$ts.zip" - val archivePath = "${filesDir.absolutePath}${File.separator}$archiveName" + val archivePath = "${(storagePath ?: filesDir).absolutePath}${File.separator}$archiveName" val config = ArchiveConfig(archivePath, parentTempDirectory = databaseExportDir.toString()) + // Settings should be saved before changing a passphrase, otherwise the database needs to be migrated first + if (!m.chatDbChanged.value) { + controller.apiSaveAppSettings(AppSettings.current.prepareForExport()) + } m.controller.apiExportArchive(config) - deleteOldArchive(m) - m.controller.appPrefs.chatArchiveName.set(archiveName) + if (storagePath == null) { + deleteOldArchive(m) + m.controller.appPrefs.chatArchiveName.set(archiveName) + m.controller.appPrefs.chatArchiveTime.set(archiveTime) + } chatArchiveName.value = archiveName - m.controller.appPrefs.chatArchiveTime.set(archiveTime) chatArchiveTime.value = archiveTime chatArchiveFile.value = archivePath return archivePath diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt index 1a29a334a8..2fb27e29b1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/CloseSheetBar.kt @@ -18,7 +18,7 @@ import chat.simplex.res.MR import dev.icerock.moko.resources.compose.painterResource @Composable -fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, endButtons: @Composable RowScope.() -> Unit = {}) { +fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Color = if (close != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, endButtons: @Composable RowScope.() -> Unit = {}) { Column( Modifier .fillMaxWidth() @@ -35,7 +35,7 @@ fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, endButtons: @ verticalAlignment = Alignment.CenterVertically ) { if (showClose) { - NavigationButtonBack(onButtonClicked = close) + NavigationButtonBack(tintColor = tintColor, onButtonClicked = close) } else { Spacer(Modifier) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt index 52dc2c0658..0ad7af439f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DatabaseUtils.kt @@ -64,7 +64,7 @@ object DatabaseUtils { return dbKey } - private fun randomDatabasePassword(): String { + fun randomDatabasePassword(): String { val s = ByteArray(32) SecureRandom().nextBytes(s) return s.toBase64StringForPassphrase().replace("\n", "") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultProgressBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultProgressBar.kt index 104a01150f..675584ae13 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultProgressBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultProgressBar.kt @@ -15,7 +15,7 @@ fun DefaultProgressView(description: String?) { Column(horizontalAlignment = Alignment.CenterHorizontally) { CircularProgressIndicator( Modifier - .padding(bottom = DEFAULT_PADDING) + .padding(bottom = if (description != null) DEFAULT_PADDING else 0.dp) .size(30.dp), color = MaterialTheme.colors.secondary, strokeWidth = 2.5.dp diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt index 93be24d921..577411c7e3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/DefaultTopAppBar.kt @@ -44,10 +44,10 @@ fun DefaultTopAppBar( } @Composable -fun NavigationButtonBack(onButtonClicked: (() -> Unit)?) { +fun NavigationButtonBack(onButtonClicked: (() -> Unit)?, tintColor: Color = if (onButtonClicked != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary) { IconButton(onButtonClicked ?: {}, enabled = onButtonClicked != null) { Icon( - painterResource(MR.images.ic_arrow_back_ios_new), stringResource(MR.strings.back), tint = if (onButtonClicked != null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary + painterResource(MR.images.ic_arrow_back_ios_new), stringResource(MR.strings.back), tint = tintColor ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt index e2dd315fb0..887a5bfdd9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ModalView.kt @@ -19,17 +19,18 @@ import kotlin.math.min fun ModalView( close: () -> Unit, showClose: Boolean = true, + enableClose: Boolean = true, background: Color = MaterialTheme.colors.background, modifier: Modifier = Modifier, endButtons: @Composable RowScope.() -> Unit = {}, content: @Composable () -> Unit, ) { if (showClose) { - BackHandler(onBack = close) + BackHandler(enabled = enableClose, onBack = close) } Surface(Modifier.fillMaxSize(), contentColor = LocalContentColor.current) { Column(if (background != MaterialTheme.colors.background) Modifier.background(background) else Modifier.themedBackground()) { - CloseSheetBar(close, showClose, endButtons) + CloseSheetBar(if (enableClose) close else null, showClose, endButtons = endButtons) Box(modifier) { content() } } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt index 1c3540d7f2..d930a2841e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Section.kt @@ -61,9 +61,8 @@ fun <T> SectionViewSelectable( onSelected: (T) -> Unit, ) { SectionView(title) { - LazyColumn { - items(values.size) { index -> - val item = values[index] + Column { + values.forEach { item -> SectionItemViewSpaceBetween({ onSelected(item.value) }) { Text(item.title) if (currentValue.value == item.value) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt index cfe58f25c0..ae09163591 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/Utils.kt @@ -411,7 +411,7 @@ expect fun ByteArray.toBase64StringForPassphrase(): String // Android's default implementation that was used before multiplatform, adds non-needed characters at the end of string // which can be bypassed by: -// fun String.toByteArrayFromBase64(): ByteArray = Base64.getDecoder().decode(this.trimEnd { it == '\n' || it == ' ' }) +// fun String.toByteArrayFromBase64(): ByteArray = Base64.getMimeDecoder().decode(this.trimEnd { it == '\n' || it == ' ' }) expect fun String.toByteArrayFromBase64ForPassphrase(): ByteArray val LongRange.Companion.saver diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt new file mode 100644 index 0000000000..f8e3d48625 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateFromDevice.kt @@ -0,0 +1,715 @@ +package chat.simplex.common.views.migration + +import SectionBottomSpacer +import SectionSpacer +import SectionTextFooter +import SectionView +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.rotate +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.sp +import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.getNetCfg +import chat.simplex.common.model.ChatController.startChat +import chat.simplex.common.model.ChatController.startChatWithTemporaryDatabase +import chat.simplex.common.model.ChatCtrl +import chat.simplex.common.model.ChatModel.controller +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.database.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.newchat.LinkTextView +import chat.simplex.common.views.newchat.SimpleXLinkQRCode +import chat.simplex.common.views.usersettings.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.* +import kotlinx.datetime.* +import kotlinx.serialization.* +import java.io.File +import java.net.URLEncoder +import kotlin.math.max + +@Serializable +data class MigrationFileLinkData( + val networkConfig: NetworkConfig?, +) { + @Serializable + data class NetworkConfig( + val socksProxy: String?, + val hostMode: HostMode?, + val requiredHostMode: Boolean? + ) { + fun hasOnionConfigured(): Boolean = socksProxy != null || hostMode == HostMode.Onion + + fun transformToPlatformSupported(): NetworkConfig { + return if (hostMode != null && requiredHostMode != null) { + NetworkConfig( + socksProxy = if (hostMode == HostMode.Onion) socksProxy ?: NetCfg.proxyDefaults.socksProxy else socksProxy, + hostMode = if (hostMode == HostMode.Onion) HostMode.OnionViaSocks else hostMode, + requiredHostMode = requiredHostMode + ) + } else this + } + } + + fun addToLink(link: String) = link + "&data=" + URLEncoder.encode(jsonShort.encodeToString(this), "UTF-8") + + companion object { + suspend fun readFromLink(link: String): MigrationFileLinkData? = + try { + // val data = link.substringAfter("&data=").substringBefore("&") + // json.decodeFromString(URLDecoder.decode(data, "UTF-8")) + controller.standaloneFileInfo(link) + } catch (e: Exception) { + null + } + } +} + + + +@Serializable +private sealed class MigrationFromState { + @Serializable object ChatStopInProgress: MigrationFromState() + @Serializable data class ChatStopFailed(val reason: String): MigrationFromState() + @Serializable object PassphraseNotSet: MigrationFromState() + @Serializable object PassphraseConfirmation: MigrationFromState() + @Serializable object UploadConfirmation: MigrationFromState() + @Serializable object Archiving: MigrationFromState() + @Serializable data class DatabaseInit(val totalBytes: Long, val archivePath: String): MigrationFromState() + @Serializable data class UploadProgress(val uploadedBytes: Long, val totalBytes: Long, val fileId: Long, val archivePath: String, val ctrl: ChatCtrl, val user: User): MigrationFromState() + @Serializable data class UploadFailed(val totalBytes: Long, val archivePath: String): MigrationFromState() + @Serializable object LinkCreation: MigrationFromState() + @Serializable data class LinkShown(val fileId: Long, val link: String, val ctrl: ChatCtrl): MigrationFromState() + @Serializable data class Finished(val chatDeletion: Boolean): MigrationFromState() +} + +private var MutableState<MigrationFromState>.state: MigrationFromState + get() = value + set(v) { value = v } + +@Composable +fun MigrateFromDeviceView(close: () -> Unit) { + val migrationState = rememberSaveable(stateSaver = serializableSaver()) { mutableStateOf<MigrationFromState>(MigrationFromState.ChatStopInProgress) } + // Prevent from hiding the view until migration is finished or app deleted + val backDisabled = remember { + derivedStateOf { + when (migrationState.value) { + is MigrationFromState.ChatStopInProgress, + is MigrationFromState.DatabaseInit, + is MigrationFromState.Archiving, + is MigrationFromState.LinkShown, + is MigrationFromState.Finished -> true + + is MigrationFromState.ChatStopFailed, + is MigrationFromState.PassphraseNotSet, + is MigrationFromState.PassphraseConfirmation, + is MigrationFromState.UploadConfirmation, + is MigrationFromState.UploadProgress, + is MigrationFromState.UploadFailed, + is MigrationFromState.LinkCreation -> false + } + } + } + val chatReceiver = remember { mutableStateOf(null as MigrationFromChatReceiver?) } + ModalView( + enableClose = !backDisabled.value, + close = { + withBGApi { + migrationState.cleanUpOnBack(chatReceiver.value) + } + close() + }, + ) { + MigrateFromDeviceLayout( + migrationState = migrationState, + chatReceiver = chatReceiver + ) + } +} + +@Composable +private fun MigrateFromDeviceLayout( + migrationState: MutableState<MigrationFromState>, + chatReceiver: MutableState<MigrationFromChatReceiver?> +) { + val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) } + + val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents() + val scrollState = rememberScrollState() + Column( + Modifier.fillMaxSize().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier).height(IntrinsicSize.Max), + ) { + AppBarTitle(stringResource(MR.strings.migrate_from_device_title)) + SectionByState(migrationState, tempDatabaseFile.value, chatReceiver) + SectionBottomSpacer() + } + if (appPlatform.isDesktop) { + Box(Modifier.fillMaxSize()) { + platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false) + } + } + platform.androidLockPortraitOrientation() +} + +@Composable +private fun SectionByState( + migrationState: MutableState<MigrationFromState>, + tempDatabaseFile: File, + chatReceiver: MutableState<MigrationFromChatReceiver?> +) { + when (val s = migrationState.value) { + is MigrationFromState.ChatStopInProgress -> migrationState.ChatStopInProgressView() + is MigrationFromState.ChatStopFailed -> migrationState.ChatStopFailedView(s.reason) + is MigrationFromState.PassphraseNotSet -> migrationState.PassphraseNotSetView() + is MigrationFromState.PassphraseConfirmation -> migrationState.PassphraseConfirmationView() + is MigrationFromState.UploadConfirmation -> migrationState.UploadConfirmationView() + is MigrationFromState.Archiving -> migrationState.ArchivingView() + is MigrationFromState.DatabaseInit -> migrationState.DatabaseInitView(tempDatabaseFile, s.totalBytes, s.archivePath) + is MigrationFromState.UploadProgress -> migrationState.UploadProgressView(s.uploadedBytes, s.totalBytes, s.ctrl, s.user, tempDatabaseFile, chatReceiver, s.archivePath) + is MigrationFromState.UploadFailed -> migrationState.UploadFailedView(s.totalBytes, s.archivePath, chatReceiver.value) + is MigrationFromState.LinkCreation -> LinkCreationView() + is MigrationFromState.LinkShown -> migrationState.LinkShownView(s.fileId, s.link, s.ctrl) + is MigrationFromState.Finished -> migrationState.FinishedView(s.chatDeletion) + } +} + +@Composable +private fun MutableState<MigrationFromState>.ChatStopInProgressView() { + Box { + SectionView(stringResource(MR.strings.migrate_from_device_stopping_chat).uppercase()) {} + ProgressView() + } + LaunchedEffect(Unit) { + stopChat() + } +} + +@Composable +private fun MutableState<MigrationFromState>.ChatStopFailedView(reason: String) { + SectionView(stringResource(MR.strings.error_stopping_chat).uppercase()) { + Text(reason) + SectionSpacer() + SettingsActionItemWithContent( + icon = painterResource(MR.images.ic_report_filled), + text = stringResource(MR.strings.auth_stop_chat), + textColor = MaterialTheme.colors.error, + click = ::stopChat + ){} + SectionTextFooter(stringResource(MR.strings.migrate_from_device_chat_should_be_stopped)) + } +} + +@Composable +private fun MutableState<MigrationFromState>.PassphraseNotSetView() { + DatabaseEncryptionView(chatModel, true) + KeyChangeEffect(appPreferences.initialRandomDBPassphrase.state.value) { + if (!appPreferences.initialRandomDBPassphrase.get()) { + state = MigrationFromState.UploadConfirmation + } + } +} + +@Composable +private fun MutableState<MigrationFromState>.PassphraseConfirmationView() { + val useKeychain = remember { appPreferences.storeDBPassphrase.get() } + val currentKey = rememberSaveable { mutableStateOf("") } + val verifyingPassphrase = rememberSaveable { mutableStateOf(false) } + Box { + val view = LocalMultiplatformView() + Column { + ChatStoppedView() + SectionSpacer() + + SectionView(stringResource(MR.strings.migrate_from_device_verify_database_passphrase).uppercase()) { + PassphraseField(currentKey, placeholder = stringResource(MR.strings.current_passphrase), Modifier.padding(horizontal = DEFAULT_PADDING), isValid = ::validKey, requestFocus = true) + + SettingsActionItemWithContent( + icon = painterResource(if (useKeychain) MR.images.ic_vpn_key_filled else MR.images.ic_lock), + text = stringResource(MR.strings.migrate_from_device_verify_passphrase), + textColor = MaterialTheme.colors.primary, + disabled = verifyingPassphrase.value || currentKey.value.isEmpty(), + click = { + verifyingPassphrase.value = true + hideKeyboard(view) + withBGApi { + verifyDatabasePassphrase(currentKey.value) + verifyingPassphrase.value = false + } + } + ) {} + SectionTextFooter(stringResource(MR.strings.migrate_from_device_confirm_you_remember_passphrase)) + } + } + if (verifyingPassphrase.value) { + ProgressView() + } + } +} + +@Composable +private fun MutableState<MigrationFromState>.UploadConfirmationView() { + SectionView(stringResource(MR.strings.migrate_from_device_confirm_upload).uppercase()) { + SettingsActionItemWithContent( + icon = painterResource(MR.images.ic_ios_share), + text = stringResource(MR.strings.migrate_from_device_archive_and_upload), + textColor = MaterialTheme.colors.primary, + click = { state = MigrationFromState.Archiving } + ){} + SectionTextFooter(stringResource(MR.strings.migrate_from_device_all_data_will_be_uploaded)) + } +} + +@Composable +private fun MutableState<MigrationFromState>.ArchivingView() { + Box { + SectionView(stringResource(MR.strings.migrate_from_device_archiving_database).uppercase()) {} + ProgressView() + } + LaunchedEffect(Unit) { + exportArchive() + } +} + +@Composable +private fun MutableState<MigrationFromState>.DatabaseInitView(tempDatabaseFile: File, totalBytes: Long, archivePath: String) { + Box { + SectionView(stringResource(MR.strings.migrate_from_device_database_init).uppercase()) {} + ProgressView() + } + LaunchedEffect(Unit) { + prepareDatabase(tempDatabaseFile, totalBytes, archivePath) + } +} + +@Composable +private fun MutableState<MigrationFromState>.UploadProgressView( + uploadedBytes: Long, + totalBytes: Long, + ctrl: ChatCtrl, + user: User, + tempDatabaseFile: File, + chatReceiver: MutableState<MigrationFromChatReceiver?>, + archivePath: String, +) { + Box { + SectionView(stringResource(MR.strings.migrate_from_device_uploading_archive).uppercase()) { + val ratio = uploadedBytes.toFloat() / max(totalBytes, 1) + LargeProgressView(ratio, "${(ratio * 100).toInt()}%", stringResource(MR.strings.migrate_from_device_bytes_uploaded).format(formatBytes(uploadedBytes))) + } + } + LaunchedEffect(Unit) { + startUploading(totalBytes, ctrl, user, tempDatabaseFile, chatReceiver, archivePath) + } +} + +@Composable +private fun MutableState<MigrationFromState>.UploadFailedView(totalBytes: Long, archivePath: String, chatReceiver: MigrationFromChatReceiver?) { + SectionView(stringResource(MR.strings.migrate_from_device_upload_failed).uppercase()) { + SettingsActionItemWithContent( + icon = painterResource(MR.images.ic_ios_share), + text = stringResource(MR.strings.migrate_from_device_repeat_upload), + textColor = MaterialTheme.colors.primary, + click = { + state = MigrationFromState.DatabaseInit(totalBytes, archivePath) + } + ) {} + SectionTextFooter(stringResource(MR.strings.migrate_from_device_try_again)) + } + LaunchedEffect(Unit) { + chatReceiver?.stopAndCleanUp() + } +} + +@Composable +private fun LinkCreationView() { + Box { + SectionView(stringResource(MR.strings.migrate_from_device_creating_archive_link).uppercase()) {} + ProgressView() + } +} + +@Composable +private fun MutableState<MigrationFromState>.LinkShownView(fileId: Long, link: String, ctrl: ChatCtrl) { + SectionView { + SettingsActionItemWithContent( + icon = painterResource(MR.images.ic_close), + text = stringResource(MR.strings.migrate_from_device_cancel_migration), + textColor = MaterialTheme.colors.error, + click = { + cancelMigration(fileId, ctrl) + } + ) {} + SettingsActionItemWithContent( + icon = painterResource(MR.images.ic_check), + text = stringResource(MR.strings.migrate_from_device_finalize_migration), + textColor = MaterialTheme.colors.primary, + click = { + finishMigration(fileId, ctrl) + } + ) {} + SectionTextFooter(annotatedStringResource(MR.strings.migrate_from_device_archive_will_be_deleted)) + SectionTextFooter(annotatedStringResource(MR.strings.migrate_from_device_choose_migrate_from_another_device)) + } + SectionSpacer() + SectionView(stringResource(MR.strings.show_QR_code).uppercase()) { + SimpleXLinkQRCode(link, onShare = {}) + } + SectionSpacer() + SectionView(stringResource(MR.strings.migrate_from_device_or_share_this_file_link).uppercase()) { + LinkTextView(link, true) + } +} + +@Composable +private fun MutableState<MigrationFromState>.FinishedView(chatDeletion: Boolean) { + Box { + SectionView(stringResource(MR.strings.migrate_from_device_migration_complete).uppercase()) { + SettingsActionItemWithContent( + icon = painterResource(MR.images.ic_play_arrow_filled), + text = stringResource(MR.strings.migrate_from_device_start_chat), + textColor = MaterialTheme.colors.error, + click = { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.start_chat_question), + text = generalGetString(MR.strings.migrate_from_device_starting_chat_on_multiple_devices_unsupported), + confirmText = generalGetString(MR.strings.migrate_from_device_start_chat), + onConfirm = { + withLongRunningApi { startChatAndDismiss() } + }, + destructive = true + ) + } + ) {} + + SettingsActionItemWithContent( + icon = painterResource(MR.images.ic_delete_forever), + text = stringResource(MR.strings.migrate_from_device_delete_database_from_device), + textColor = MaterialTheme.colors.primary, + click = { + AlertManager.shared.showAlertDialog( + title = generalGetString(MR.strings.delete_chat_profile_question), + text = generalGetString(MR.strings.delete_chat_profile_action_cannot_be_undone_warning), + confirmText = generalGetString(MR.strings.delete_verb), + onConfirm = { + deleteChatAndDismiss() + } + ) + } + ) {} + SectionTextFooter(annotatedStringResource(MR.strings.migrate_from_device_you_must_not_start_database_on_two_device)) + SectionTextFooter(annotatedStringResource(MR.strings.migrate_from_device_using_on_two_device_breaks_encryption)) + } + if (chatDeletion) { + ProgressView() + } + } +} + +@Composable +private fun ProgressView() { + DefaultProgressView(null) +} + +@Composable +fun LargeProgressView(value: Float, title: String, description: String) { + Box(Modifier.padding(DEFAULT_PADDING).fillMaxSize(), contentAlignment = Alignment.Center) { + CircularProgressIndicator( + progress = value, + (if (appPlatform.isDesktop) Modifier.size(DEFAULT_START_MODAL_WIDTH) else Modifier.size(windowWidth() - DEFAULT_PADDING * 2)) + .rotate(-90f), + color = MaterialTheme.colors.primary, + strokeWidth = 25.dp + ) + Column(horizontalAlignment = Alignment.CenterHorizontally) { + Text(description, color = Color.Transparent) + Text(title, style = MaterialTheme.typography.h1.copy(fontSize = 50.sp, fontWeight = FontWeight.Bold), color = MaterialTheme.colors.primary) + Text(description, style = MaterialTheme.typography.subtitle1) + } + } +} + +private fun MutableState<MigrationFromState>.stopChat() { + withBGApi { + try { + stopChatAsync(chatModel) + try { + controller.apiSaveAppSettings(AppSettings.current.prepareForExport()) + state = if (appPreferences.initialRandomDBPassphrase.get()) MigrationFromState.PassphraseNotSet else MigrationFromState.PassphraseConfirmation + } catch (e: Exception) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.migrate_from_device_error_saving_settings), + text = e.stackTraceToString() + ) + state = MigrationFromState.ChatStopFailed(reason = generalGetString(MR.strings.migrate_from_device_error_saving_settings)) + } + } catch (e: Exception) { + state = MigrationFromState.ChatStopFailed(reason = e.stackTraceToString().take(10)) + } + } +} + +private suspend fun MutableState<MigrationFromState>.verifyDatabasePassphrase(dbKey: String) { + val error = controller.testStorageEncryption(dbKey) + if (error == null) { + state = MigrationFromState.UploadConfirmation + } else if (((error.chatError as? ChatError.ChatErrorDatabase)?.databaseError as? DatabaseError.ErrorOpen)?.sqliteError is SQLiteError.ErrorNotADatabase) { + showErrorOnMigrationIfNeeded(DBMigrationResult.ErrorNotADatabase("")) + } else { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.error), + text = generalGetString(MR.strings.migrate_from_device_error_verifying_passphrase) + " " + error.details + ) + } +} + +private fun MutableState<MigrationFromState>.exportArchive() { + withLongRunningApi { + try { + getMigrationTempFilesDirectory().mkdir() + val archivePath = exportChatArchive(chatModel, getMigrationTempFilesDirectory(), mutableStateOf(""), mutableStateOf(Instant.DISTANT_PAST), mutableStateOf("")) + val totalBytes = File(archivePath).length() + if (totalBytes > 0L) { + state = MigrationFromState.DatabaseInit(totalBytes, archivePath) + } else { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.migrate_from_device_exported_file_doesnt_exist)) + state = MigrationFromState.UploadConfirmation + } + } catch (e: Exception) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.migrate_from_device_error_exporting_archive), + text = e.stackTraceToString() + ) + state = MigrationFromState.UploadConfirmation + } + } +} + +suspend fun initTemporaryDatabase(tempDatabaseFile: File, netCfg: NetCfg): Pair<ChatCtrl, User>? { + val (status, ctrl) = chatInitTemporaryDatabase(tempDatabaseFile.absolutePath) + showErrorOnMigrationIfNeeded(status) + try { + if (ctrl != null) { + val user = startChatWithTemporaryDatabase(ctrl, netCfg) + return if (user != null) ctrl to user else null + } + } catch (e: Throwable) { + Log.e(TAG, "Error while starting chat in temporary database: ${e.stackTraceToString()}") + } + return null +} + +private fun MutableState<MigrationFromState>.prepareDatabase( + tempDatabaseFile: File, + totalBytes: Long, + archivePath: String, +) { + withLongRunningApi { + val ctrlAndUser = initTemporaryDatabase(tempDatabaseFile, getNetCfg()) + if (ctrlAndUser == null) { + state = MigrationFromState.UploadFailed(totalBytes, archivePath) + return@withLongRunningApi + } + + val (ctrl, user) = ctrlAndUser + state = MigrationFromState.UploadProgress(0L, totalBytes, 0L, archivePath, ctrl, user) + } +} + +private fun MutableState<MigrationFromState>.startUploading( + totalBytes: Long, + ctrl: ChatCtrl, + user: User, + tempDatabaseFile: File, + chatReceiver: MutableState<MigrationFromChatReceiver?>, + archivePath: String, +) { + withBGApi { + chatReceiver.value = MigrationFromChatReceiver(ctrl, tempDatabaseFile) { msg -> + when (msg) { + is CR.SndFileProgressXFTP -> { + val s = state + if (s is MigrationFromState.UploadProgress && s.uploadedBytes != s.totalBytes) { + state = MigrationFromState.UploadProgress(msg.sentSize, msg.totalSize, msg.fileTransferMeta.fileId, archivePath, ctrl, user) + } + } + is CR.SndFileRedirectStartXFTP -> { + delay(500) + state = MigrationFromState.LinkCreation + } + is CR.SndStandaloneFileComplete -> { + delay(500) + val cfg = getNetCfg() + val data = MigrationFileLinkData( + networkConfig = MigrationFileLinkData.NetworkConfig( + socksProxy = cfg.socksProxy, + hostMode = cfg.hostMode, + requiredHostMode = cfg.requiredHostMode + ) + ) + state = MigrationFromState.LinkShown(msg.fileTransferMeta.fileId, data.addToLink(msg.rcvURIs[0]), ctrl) + } + is CR.SndFileError -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.migrate_from_device_upload_failed), + generalGetString(MR.strings.migrate_from_device_check_connection_and_try_again) + ) + state = MigrationFromState.UploadFailed(totalBytes, archivePath) + } + else -> { + Log.d(TAG, "unsupported event: ${msg.responseType}") + } + } + } + + chatReceiver.value?.start() + + val (res, error) = controller.uploadStandaloneFile(user, CryptoFile.plain(File(archivePath).name), ctrl) + if (res == null) { + state = MigrationFromState.UploadFailed(totalBytes, archivePath) + return@withBGApi AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.migrate_from_device_error_uploading_archive), + error + ) + } + state = MigrationFromState.UploadProgress(0, res.fileSize, res.fileId, archivePath, ctrl, user) + } +} + +private suspend fun cancelUploadedArchive(fileId: Long, ctrl: ChatCtrl) { + controller.apiCancelFile(null, fileId, ctrl) +} + +private fun cancelMigration(fileId: Long, ctrl: ChatCtrl) { + withBGApi { + cancelUploadedArchive(fileId, ctrl) + startChatAndDismiss() + } +} + +private fun MutableState<MigrationFromState>.finishMigration(fileId: Long, ctrl: ChatCtrl) { + withBGApi { + cancelUploadedArchive(fileId, ctrl) + state = MigrationFromState.Finished(false) + } +} + +private fun MutableState<MigrationFromState>.deleteChatAndDismiss() { + withBGApi { + try { + deleteChatAsync(chatModel) + chatModel.chatDbChanged.value = true + state = MigrationFromState.Finished(true) + try { + initChatController(startChat = { CompletableDeferred(false) }) + chatModel.chatDbChanged.value = false + ModalManager.fullscreen.closeModals() + } catch (e: Exception) { + throw Exception(generalGetString(MR.strings.error_starting_chat) + "\n" + e.stackTraceToString()) + } + } catch (e: Exception) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.migrate_from_device_error_deleting_database), + text = e.stackTraceToString() + ) + } + } +} + +private suspend fun startChatAndDismiss(dismiss: Boolean = true) { + try { + val user = chatModel.currentUser.value + if (chatModel.chatDbChanged.value) { + initChatController() + chatModel.chatDbChanged.value = false + } else if (user != null) { + startChat(user) + } + } catch (e: Exception) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.error_starting_chat), + text = e.stackTraceToString() + ) + } + // Hide settings anyway if chatDbStatus is not ok, probably passphrase needs to be entered + if (dismiss || chatModel.chatDbStatus.value != DBMigrationResult.OK) { + ModalManager.fullscreen.closeModals() + } +} + +private suspend fun MutableState<MigrationFromState>.cleanUpOnBack(chatReceiver: MigrationFromChatReceiver?) { + val s = state + if (s !is MigrationFromState.LinkShown && s !is MigrationFromState.Finished) { + chatModel.switchingUsersAndHosts.value = true + startChatAndDismiss(false) + chatModel.switchingUsersAndHosts.value = false + } + if (s is MigrationFromState.UploadProgress) { + cancelUploadedArchive(s.fileId, s.ctrl) + } + chatReceiver?.stopAndCleanUp() + getMigrationTempFilesDirectory().deleteRecursively() +} + +private fun fileForTemporaryDatabase(): File = + File(getMigrationTempFilesDirectory(), generateNewFileName("migration", "db", getMigrationTempFilesDirectory())) + +private class MigrationFromChatReceiver( + val ctrl: ChatCtrl, + val databaseUrl: File, + var receiveMessages: Boolean = true, + val processReceivedMsg: suspend (CR) -> Unit +) { + fun start() { + Log.d(TAG, "MigrationChatReceiver startReceiver") + CoroutineScope(Dispatchers.IO).launch { + while (receiveMessages) { + try { + val msg = ChatController.recvMsg(ctrl) + if (msg != null && receiveMessages) { + val r = msg.resp + val rhId = msg.remoteHostId + Log.d(TAG, "processReceivedMsg: ${r.responseType}") + chatModel.addTerminalItem(TerminalItem.resp(rhId, r)) + val finishedWithoutTimeout = withTimeoutOrNull(60_000L) { + processReceivedMsg(r) + } + if (finishedWithoutTimeout == null) { + Log.e(TAG, "Timeout reached while processing received message: " + msg.resp.responseType) + if (appPreferences.developerTools.get() && appPreferences.showSlowApiCalls.get()) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.possible_slow_function_title), + text = generalGetString(MR.strings.possible_slow_function_desc).format(60, msg.resp.responseType + "\n" + Exception().stackTraceToString()), + shareText = true + ) + } + } + } + } catch (e: Exception) { + Log.e(TAG, "MigrationChatReceiver recvMsg/processReceivedMsg exception: " + e.stackTraceToString()) + } catch (e: Exception) { + Log.e(TAG, "MigrationChatReceiver recvMsg/processReceivedMsg throwable: " + e.stackTraceToString()) + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), e.stackTraceToString()) + } + } + } + } + + fun stopAndCleanUp() { + Log.d(TAG, "MigrationChatReceiver.stop") + receiveMessages = false + chatCloseStore(ctrl) + File(databaseUrl.absolutePath + "_chat.db").delete() + File(databaseUrl.absolutePath + "_agent.db").delete() + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt new file mode 100644 index 0000000000..9d204ad42c --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -0,0 +1,744 @@ +package chat.simplex.common.views.migration + +import SectionBottomSpacer +import SectionItemView +import SectionSpacer +import SectionTextFooter +import SectionView +import androidx.compose.foundation.* +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.runtime.* +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalClipboardManager +import chat.simplex.common.model.* +import chat.simplex.common.model.AppPreferences.Companion.SHARED_PREFS_MIGRATION_TO_STAGE +import chat.simplex.common.model.ChatController.getNetCfg +import chat.simplex.common.model.ChatController.startChat +import chat.simplex.common.model.ChatCtrl +import chat.simplex.common.model.ChatModel.controller +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.database.* +import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.helpers.DatabaseUtils.ksDatabasePassword +import chat.simplex.common.views.newchat.QRCodeScanner +import chat.simplex.common.views.onboarding.OnboardingStage +import chat.simplex.common.views.usersettings.* +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.coroutines.* +import kotlinx.datetime.Clock +import kotlinx.datetime.toJavaInstant +import kotlinx.serialization.* +import java.io.File +import java.text.SimpleDateFormat +import java.util.* +import kotlin.math.max + +@Serializable +sealed class MigrationToDeviceState { + @Serializable @SerialName("onion") data class Onion(val link: String, val socksProxy: String?, val hostMode: HostMode, val requiredHostMode: Boolean): MigrationToDeviceState() + @Serializable @SerialName("downloadProgress") data class DownloadProgress(val link: String, val archiveName: String, val netCfg: NetCfg): MigrationToDeviceState() + @Serializable @SerialName("archiveImport") data class ArchiveImport(val archiveName: String, val netCfg: NetCfg): MigrationToDeviceState() + @Serializable @SerialName("passphrase") data class Passphrase(val netCfg: NetCfg): MigrationToDeviceState() + + companion object { + // Here we check whether it's needed to show migration process after app restart or not + // It's important to NOT show the process when archive was corrupted/not fully downloaded + fun makeMigrationState(): MigrationToState? { + val stage = settings.getStringOrNull(SHARED_PREFS_MIGRATION_TO_STAGE) + val state: MigrationToDeviceState? = if (stage != null) json.decodeFromString(stage) else null + val initial: MigrationToState? = when(state) { + null -> null + is DownloadProgress -> { + // No migration happens at the moment actually since archive were not downloaded fully + Log.e(TAG, "MigrateToDevice: archive wasn't fully downloaded, removed broken file") + null + } + is Onion -> null + is ArchiveImport -> { + if (!File(getMigrationTempFilesDirectory(), state.archiveName).exists()) { + Log.e(TAG, "MigrateToDevice: archive was removed unintentionally or state is broken, dropping migration") + null + } else { + val archivePath = File(getMigrationTempFilesDirectory(), state.archiveName) + MigrationToState.ArchiveImportFailed(archivePath.absolutePath, state.netCfg) + } + } + is Passphrase -> MigrationToState.Passphrase("", state.netCfg) + } + if (initial == null) { + settings.remove(SHARED_PREFS_MIGRATION_TO_STAGE) + getMigrationTempFilesDirectory().deleteRecursively() + } + return initial + } + + fun save(state: MigrationToDeviceState?) { + if (state != null) { + appPreferences.migrationToStage.set(json.encodeToString(state)) + } else { + appPreferences.migrationToStage.set(null) + } + } + } +} + +@Serializable +sealed class MigrationToState { + @Serializable object PasteOrScanLink: MigrationToState() + @Serializable data class Onion(val link: String, val socksProxy: String?, val hostMode: HostMode, val requiredHostMode: Boolean): MigrationToState() + @Serializable data class DatabaseInit(val link: String, val netCfg: NetCfg): MigrationToState() + @Serializable data class LinkDownloading(val link: String, val ctrl: ChatCtrl, val user: User, val archivePath: String, val netCfg: NetCfg): MigrationToState() + @Serializable data class DownloadProgress(val downloadedBytes: Long, val totalBytes: Long, val fileId: Long, val link: String, val archivePath: String, val netCfg: NetCfg, val ctrl: ChatCtrl?): MigrationToState() + @Serializable data class DownloadFailed(val totalBytes: Long, val link: String, val archivePath: String, val netCfg: NetCfg): MigrationToState() + @Serializable data class ArchiveImport(val archivePath: String, val netCfg: NetCfg): MigrationToState() + @Serializable data class ArchiveImportFailed(val archivePath: String, val netCfg: NetCfg): MigrationToState() + @Serializable data class Passphrase(val passphrase: String, val netCfg: NetCfg): MigrationToState() + @Serializable data class MigrationConfirmation(val status: DBMigrationResult, val passphrase: String, val useKeychain: Boolean, val netCfg: NetCfg): MigrationToState() + @Serializable data class Migration(val passphrase: String, val confirmation: chat.simplex.common.views.helpers.MigrationConfirmation, val useKeychain: Boolean, val netCfg: NetCfg): MigrationToState() +} + +private var MutableState<MigrationToState?>.state: MigrationToState? + get() = value + set(v) { value = v } + +@Composable +fun ModalData.MigrateToDeviceView(close: () -> Unit) { + val migrationState = remember { chatModel.migrationState } + // Prevent from hiding the view until migration is finished or app deleted + val backDisabled = remember { + derivedStateOf { + when (chatModel.migrationState.value) { + null, + is MigrationToState.PasteOrScanLink, + is MigrationToState.Onion, + is MigrationToState.LinkDownloading, + is MigrationToState.DownloadProgress, + is MigrationToState.DownloadFailed, + is MigrationToState.ArchiveImportFailed -> false + + is MigrationToState.ArchiveImport, + is MigrationToState.DatabaseInit, + is MigrationToState.Migration, + is MigrationToState.MigrationConfirmation, + is MigrationToState.Passphrase -> true + } + } + } + val chatReceiver = remember { mutableStateOf(null as MigrationToChatReceiver?) } + ModalView( + enableClose = !backDisabled.value, + close = { + withBGApi { + migrationState.cleanUpOnBack(chatReceiver.value) + close() + } + }, + ) { + MigrateToDeviceLayout( + migrationState = migrationState, + chatReceiver = chatReceiver, + close = close, + ) + } +} + +@Composable +private fun ModalData.MigrateToDeviceLayout( + migrationState: MutableState<MigrationToState?>, + chatReceiver: MutableState<MigrationToChatReceiver?>, + close: () -> Unit, +) { + val tempDatabaseFile = rememberSaveable { mutableStateOf(fileForTemporaryDatabase()) } + val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents() + val scrollState = rememberScrollState() + Column( + Modifier.fillMaxSize().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier).height(IntrinsicSize.Max), + ) { + AppBarTitle(stringResource(MR.strings.migrate_to_device_title)) + SectionByState(migrationState, tempDatabaseFile.value, chatReceiver, close) + SectionBottomSpacer() + } + if (appPlatform.isDesktop) { + Box(Modifier.fillMaxSize()) { + platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false) + } + } + platform.androidLockPortraitOrientation() +} + +@Composable +private fun ModalData.SectionByState( + migrationState: MutableState<MigrationToState?>, + tempDatabaseFile: File, + chatReceiver: MutableState<MigrationToChatReceiver?>, + close: () -> Unit +) { + when (val s = migrationState.value) { + null -> {} + is MigrationToState.PasteOrScanLink -> migrationState.PasteOrScanLinkView() + is MigrationToState.Onion -> OnionView(s.link, s.socksProxy, s.hostMode, s.requiredHostMode, migrationState) + is MigrationToState.DatabaseInit -> migrationState.DatabaseInitView(s.link, tempDatabaseFile, s.netCfg) + is MigrationToState.LinkDownloading -> migrationState.LinkDownloadingView(s.link, s.ctrl, s.user, s.archivePath, tempDatabaseFile, chatReceiver, s.netCfg) + is MigrationToState.DownloadProgress -> DownloadProgressView(s.downloadedBytes, totalBytes = s.totalBytes) + is MigrationToState.DownloadFailed -> migrationState.DownloadFailedView(s.link, chatReceiver.value, s.archivePath, s.netCfg) + is MigrationToState.ArchiveImport -> migrationState.ArchiveImportView(s.archivePath, s.netCfg) + is MigrationToState.ArchiveImportFailed -> migrationState.ArchiveImportFailedView(s.archivePath, s.netCfg) + is MigrationToState.Passphrase -> migrationState.PassphraseEnteringView(currentKey = s.passphrase, s.netCfg) + is MigrationToState.MigrationConfirmation -> migrationState.MigrationConfirmationView(s.status, s.passphrase, s.useKeychain, s.netCfg) + is MigrationToState.Migration -> MigrationView(s.passphrase, s.confirmation, s.useKeychain, s.netCfg, close) + } +} + +@Composable +private fun MutableState<MigrationToState?>.PasteOrScanLinkView() { + if (appPlatform.isAndroid) { + SectionView(stringResource(MR.strings.scan_QR_code).replace('\n', ' ').uppercase()) { + QRCodeScanner(showQRCodeScanner = remember { mutableStateOf(true) }) { text -> + withBGApi { checkUserLink(text) } + } + } + SectionSpacer() + } + + if (appPlatform.isDesktop || appPreferences.developerTools.get()) { + SectionView(stringResource(if (appPlatform.isAndroid) MR.strings.or_paste_archive_link else MR.strings.paste_archive_link).uppercase()) { + PasteLinkView() + } + } +} + +@Composable +private fun MutableState<MigrationToState?>.PasteLinkView() { + val clipboard = LocalClipboardManager.current + SectionItemView({ + val str = clipboard.getText()?.text ?: return@SectionItemView + withBGApi { checkUserLink(str) } + }) { + Text(stringResource(MR.strings.tap_to_paste_link)) + } +} + +@Composable +private fun ModalData.OnionView(link: String, socksProxy: String?, hostMode: HostMode, requiredHostMode: Boolean, state: MutableState<MigrationToState?>) { + val onionHosts = remember { stateGetOrPut("onionHosts") { + getNetCfg().copy(socksProxy = socksProxy, hostMode = hostMode, requiredHostMode = requiredHostMode).onionHosts + } } + val networkUseSocksProxy = remember { stateGetOrPut("networkUseSocksProxy") { socksProxy != null } } + val sessionMode = remember { stateGetOrPut("sessionMode") { TransportSessionMode.User} } + val networkProxyHostPort = remember { stateGetOrPut("networkHostProxyPort") { + var proxy = (socksProxy ?: chatModel.controller.appPrefs.networkProxyHostPort.get()) + if (proxy?.startsWith(":") == true) proxy = "localhost$proxy" + proxy + } + } + val proxyPort = remember { derivedStateOf { networkProxyHostPort.value?.split(":")?.lastOrNull()?.toIntOrNull() ?: 9050 } } + + val netCfg = rememberSaveable(stateSaver = serializableSaver()) { + mutableStateOf(getNetCfg().withOnionHosts(onionHosts.value).copy(socksProxy = socksProxy, sessionMode = sessionMode.value)) + } + + SectionView(stringResource(MR.strings.migrate_to_device_confirm_network_settings).uppercase()) { + SettingsActionItemWithContent( + icon = painterResource(MR.images.ic_check), + text = stringResource(MR.strings.migrate_to_device_apply_onion), + textColor = MaterialTheme.colors.primary, + click = { + val updated = netCfg.value + .withOnionHosts(onionHosts.value) + .withHostPort(if (networkUseSocksProxy.value) networkProxyHostPort.value else null, null) + .copy( + sessionMode = sessionMode.value + ) + withBGApi { + state.value = MigrationToState.DatabaseInit(link, updated) + } + } + ){} + SectionTextFooter(stringResource(MR.strings.migrate_to_device_confirm_network_settings_footer)) + } + + SectionSpacer() + + val networkProxyHostPortPref = SharedPreference(get = { networkProxyHostPort.value }, set = { + networkProxyHostPort.value = it + }) + SectionView(stringResource(MR.strings.network_settings_title).uppercase()) { + OnionRelatedLayout( + appPreferences.developerTools.get(), + networkUseSocksProxy, + onionHosts, + sessionMode, + networkProxyHostPortPref, + proxyPort, + toggleSocksProxy = { enable -> + networkUseSocksProxy.value = enable + }, + useOnion = { + onionHosts.value = it + }, + updateSessionMode = { + sessionMode.value = it + } + ) + } +} + +@Composable +private fun MutableState<MigrationToState?>.DatabaseInitView(link: String, tempDatabaseFile: File, netCfg: NetCfg) { + Box { + SectionView(stringResource(MR.strings.migrate_to_device_database_init).uppercase()) {} + ProgressView() + } + LaunchedEffect(Unit) { + prepareDatabase(link, tempDatabaseFile, netCfg) + } +} + +@Composable +private fun MutableState<MigrationToState?>.LinkDownloadingView( + link: String, + ctrl: ChatCtrl, + user: User, + archivePath: String, + tempDatabaseFile: File, + chatReceiver: MutableState<MigrationToChatReceiver?>, + netCfg: NetCfg +) { + Box { + SectionView(stringResource(MR.strings.migrate_to_device_downloading_details).uppercase()) {} + ProgressView() + } + LaunchedEffect(Unit) { + startDownloading(0, ctrl, user, tempDatabaseFile, chatReceiver, link, archivePath, netCfg) + } +} + +@Composable +private fun DownloadProgressView(downloadedBytes: Long, totalBytes: Long) { + Box { + SectionView(stringResource(MR.strings.migrate_to_device_downloading_archive).uppercase()) { + val ratio = downloadedBytes.toFloat() / max(totalBytes, 1) + LargeProgressView(ratio, "${(ratio * 100).toInt()}%", stringResource(MR.strings.migrate_to_device_bytes_downloaded).format(formatBytes(downloadedBytes))) + } + } +} + +@Composable +private fun MutableState<MigrationToState?>.DownloadFailedView(link: String, chatReceiver: MigrationToChatReceiver?, archivePath: String, netCfg: NetCfg) { + SectionView(stringResource(MR.strings.migrate_to_device_download_failed).uppercase()) { + SettingsActionItemWithContent( + icon = painterResource(MR.images.ic_download), + text = stringResource(MR.strings.migrate_to_device_repeat_download), + textColor = MaterialTheme.colors.primary, + click = { + state = MigrationToState.DatabaseInit(link, netCfg) + } + ) {} + SectionTextFooter(stringResource(MR.strings.migrate_to_device_try_again)) + } + LaunchedEffect(Unit) { + chatReceiver?.stopAndCleanUp() + File(archivePath).delete() + MigrationToDeviceState.save(null) + } +} + +@Composable +private fun MutableState<MigrationToState?>.ArchiveImportView(archivePath: String, netCfg: NetCfg) { + Box { + SectionView(stringResource(MR.strings.migrate_to_device_importing_archive).uppercase()) {} + ProgressView() + } + LaunchedEffect(Unit) { + importArchive(archivePath, netCfg) + } +} + +@Composable +private fun MutableState<MigrationToState?>.ArchiveImportFailedView(archivePath: String, netCfg: NetCfg) { + SectionView(stringResource(MR.strings.migrate_to_device_import_failed).uppercase()) { + SettingsActionItemWithContent( + icon = painterResource(MR.images.ic_download), + text = stringResource(MR.strings.migrate_to_device_repeat_import), + textColor = MaterialTheme.colors.primary, + click = { + state = MigrationToState.ArchiveImport(archivePath, netCfg) + } + ) {} + SectionTextFooter(stringResource(MR.strings.migrate_to_device_try_again)) + } +} + +@Composable +private fun MutableState<MigrationToState?>.PassphraseEnteringView(currentKey: String, netCfg: NetCfg) { + val currentKey = rememberSaveable { mutableStateOf(currentKey) } + val verifyingPassphrase = rememberSaveable { mutableStateOf(false) } + val useKeychain = rememberSaveable { mutableStateOf(appPreferences.storeDBPassphrase.get()) } + + Box { + val view = LocalMultiplatformView() + SectionView(stringResource(MR.strings.migrate_to_device_enter_passphrase).uppercase()) { + SavePassphraseSetting( + useKeychain.value, + false, + false, + enabled = !verifyingPassphrase.value, + smallPadding = false + ) { checked -> useKeychain.value = checked } + + PassphraseField(currentKey, placeholder = stringResource(MR.strings.current_passphrase), Modifier.padding(horizontal = DEFAULT_PADDING), isValid = ::validKey, requestFocus = true) + + SettingsActionItemWithContent( + icon = painterResource(MR.images.ic_vpn_key_filled), + text = stringResource(MR.strings.open_chat), + textColor = MaterialTheme.colors.primary, + disabled = verifyingPassphrase.value || currentKey.value.isEmpty(), + click = { + verifyingPassphrase.value = true + hideKeyboard(view) + withBGApi { + val (status, _) = chatInitTemporaryDatabase(dbAbsolutePrefixPath, key = currentKey.value, confirmation = MigrationConfirmation.YesUp) + val success = status == DBMigrationResult.OK || status == DBMigrationResult.InvalidConfirmation + if (success) { + state = MigrationToState.Migration(currentKey.value, MigrationConfirmation.YesUp, useKeychain.value, netCfg) + } else if (status is DBMigrationResult.ErrorMigration) { + state = MigrationToState.MigrationConfirmation(status, currentKey.value, useKeychain.value, netCfg) + } else { + showErrorOnMigrationIfNeeded(status) + } + verifyingPassphrase.value = false + } + } + ) {} + DatabaseEncryptionFooter(useKeychain, chatDbEncrypted = true, remember { mutableStateOf(false) }, remember { mutableStateOf(false) }, true) + } + if (verifyingPassphrase.value) { + ProgressView() + } + } +} + +@Composable +private fun MutableState<MigrationToState?>.MigrationConfirmationView(status: DBMigrationResult, passphrase: String, useKeychain: Boolean, netCfg: NetCfg) { + data class Tuple4<A,B,C,D>(val a: A, val b: B, val c: C, val d: D) + val (header: String, button: String?, footer: String, confirmation: MigrationConfirmation?) = when (status) { + is DBMigrationResult.ErrorMigration -> when (val err = status.migrationError) { + is MigrationError.Upgrade -> + Tuple4( + generalGetString(MR.strings.database_upgrade), + generalGetString(MR.strings.upgrade_and_open_chat), + "", + MigrationConfirmation.YesUp + ) + is MigrationError.Downgrade -> + Tuple4( + generalGetString(MR.strings.database_downgrade), + generalGetString(MR.strings.downgrade_and_open_chat), + generalGetString(MR.strings.database_downgrade_warning), + MigrationConfirmation.YesUpDown + ) + is MigrationError.Error -> + Tuple4( + generalGetString(MR.strings.incompatible_database_version), + null, + mtrErrorDescription(err.mtrError), + null + ) + } + else -> Tuple4(generalGetString(MR.strings.error), null, generalGetString(MR.strings.unknown_error), null) + } + SectionView(header.uppercase()) { + if (button != null && confirmation != null) { + SettingsActionItemWithContent( + icon = painterResource(MR.images.ic_download), + text = button, + textColor = MaterialTheme.colors.primary, + click = { + state = MigrationToState.Migration(passphrase, confirmation, useKeychain, netCfg) + } + ) {} + } + SectionTextFooter(footer) + } +} + +@Composable +private fun MigrationView(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, close: () -> Unit) { + Box { + SectionView(stringResource(MR.strings.migrate_to_device_migrating).uppercase()) {} + ProgressView() + } + LaunchedEffect(Unit) { + startChat(passphrase, confirmation, useKeychain, netCfg, close) + } +} + +@Composable +private fun ProgressView() { + DefaultProgressView(null) +} + +private suspend fun MutableState<MigrationToState?>.checkUserLink(link: String) { + if (strHasSimplexFileLink(link.trim())) { + val data = MigrationFileLinkData.readFromLink(link) + val hasOnionConfigured = data?.networkConfig?.hasOnionConfigured() ?: false + val networkConfig = data?.networkConfig?.transformToPlatformSupported() + // If any of iOS or Android had onion enabled, show onion screen + if (hasOnionConfigured && networkConfig?.hostMode != null && networkConfig.requiredHostMode != null) { + state = MigrationToState.Onion(link.trim(), networkConfig.socksProxy, networkConfig.hostMode, networkConfig.requiredHostMode) + MigrationToDeviceState.save(MigrationToDeviceState.Onion(link.trim(), networkConfig.socksProxy, networkConfig.hostMode, networkConfig.requiredHostMode)) + } else { + val current = getNetCfg() + state = MigrationToState.DatabaseInit(link.trim(), current.copy( + socksProxy = networkConfig?.socksProxy, + hostMode = networkConfig?.hostMode ?: current.hostMode, + requiredHostMode = networkConfig?.requiredHostMode ?: current.requiredHostMode + )) + } + } else { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.invalid_file_link), + text = generalGetString(MR.strings.the_text_you_pasted_is_not_a_link) + ) + } +} + +private fun MutableState<MigrationToState?>.prepareDatabase( + link: String, + tempDatabaseFile: File, + netCfg: NetCfg, +) { + withLongRunningApi { + val ctrlAndUser = initTemporaryDatabase(tempDatabaseFile, netCfg) + if (ctrlAndUser == null) { + state = MigrationToState.DownloadFailed(0, link, archivePath(), netCfg) + return@withLongRunningApi + } + + val (ctrl, user) = ctrlAndUser + state = MigrationToState.LinkDownloading(link, ctrl, user, archivePath(), netCfg) + } +} + +private fun MutableState<MigrationToState?>.startDownloading( + totalBytes: Long, + ctrl: ChatCtrl, + user: User, + tempDatabaseFile: File, + chatReceiver: MutableState<MigrationToChatReceiver?>, + link: String, + archivePath: String, + netCfg: NetCfg, +) { + withBGApi { + chatReceiver.value = MigrationToChatReceiver(ctrl, tempDatabaseFile) { msg -> + when (msg) { + is CR.RcvFileProgressXFTP -> { + state = MigrationToState.DownloadProgress(msg.receivedSize, msg.totalSize, msg.rcvFileTransfer.fileId, link, archivePath, netCfg, ctrl) + MigrationToDeviceState.save(MigrationToDeviceState.DownloadProgress(link, File(archivePath).name, netCfg)) + } + is CR.RcvStandaloneFileComplete -> { + delay(500) + // User closed the whole screen before new state was saved + if (state == null) { + MigrationToDeviceState.save(null) + } else { + state = MigrationToState.ArchiveImport(archivePath, netCfg) + MigrationToDeviceState.save(MigrationToDeviceState.ArchiveImport(File(archivePath).name, netCfg)) + } + } + is CR.RcvFileError -> { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.migrate_to_device_download_failed), + generalGetString(MR.strings.migrate_to_device_file_delete_or_link_invalid) + ) + state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg) + } + is CR.ChatRespError -> { + if (msg.chatError is ChatError.ChatErrorChat && msg.chatError.errorType is ChatErrorType.NoRcvFileUser) { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.migrate_to_device_download_failed), + generalGetString(MR.strings.migrate_to_device_file_delete_or_link_invalid) + ) + state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg) + } else { + Log.d(TAG, "unsupported error: ${msg.responseType}") + } + } + else -> Log.d(TAG, "unsupported event: ${msg.responseType}") + } + } + chatReceiver.value?.start() + + val (res, error) = controller.downloadStandaloneFile(user, link, CryptoFile.plain(File(archivePath).path), ctrl) + if (res == null) { + state = MigrationToState.DownloadFailed(totalBytes, link, archivePath, netCfg) + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.migrate_to_device_error_downloading_archive), + error + ) + } + } +} + +private fun MutableState<MigrationToState?>.importArchive(archivePath: String, netCfg: NetCfg) { + withLongRunningApi { + try { + if (ChatController.ctrl == null || ChatController.ctrl == -1L) { + chatInitControllerRemovingDatabases() + } + controller.apiDeleteStorage() + try { + val config = ArchiveConfig(archivePath, parentTempDirectory = databaseExportDir.toString()) + val archiveErrors = controller.apiImportArchive(config) + if (archiveErrors.isNotEmpty()) { + AlertManager.shared.showAlertMsg( + generalGetString(MR.strings.chat_database_imported), + generalGetString(MR.strings.non_fatal_errors_occured_during_import) + ) + } + state = MigrationToState.Passphrase("", netCfg) + MigrationToDeviceState.save(MigrationToDeviceState.Passphrase(netCfg)) + } catch (e: Exception) { + state = MigrationToState.ArchiveImportFailed(archivePath, netCfg) + AlertManager.shared.showAlertMsg (generalGetString(MR.strings.error_importing_database), e.stackTraceToString()) + } + } catch (e: Exception) { + state = MigrationToState.ArchiveImportFailed(archivePath, netCfg) + AlertManager.shared.showAlertMsg (generalGetString(MR.strings.error_deleting_database), e.stackTraceToString()) + } + } +} + +private suspend fun stopArchiveDownloading(fileId: Long, ctrl: ChatCtrl) { + controller.apiCancelFile(null, fileId, ctrl) +} + +private fun startChat(passphrase: String, confirmation: MigrationConfirmation, useKeychain: Boolean, netCfg: NetCfg, close: () -> Unit) { + if (useKeychain) { + ksDatabasePassword.set(passphrase) + } else { + ksDatabasePassword.remove() + } + appPreferences.storeDBPassphrase.set(useKeychain) + appPreferences.initialRandomDBPassphrase.set(false) + withBGApi { + try { + initChatController(useKey = passphrase, confirmMigrations = confirmation) { CompletableDeferred(false) } + val appSettings = controller.apiGetAppSettings(AppSettings.current.prepareForExport()).copy( + networkConfig = netCfg + ) + finishMigration(appSettings, close) + } catch (e: Exception) { + hideView(close) + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_starting_chat), e.stackTraceToString()) + } + } +} + +private suspend fun finishMigration(appSettings: AppSettings, close: () -> Unit) { + try { + getMigrationTempFilesDirectory().deleteRecursively() + appSettings.importIntoApp() + val user = chatModel.currentUser.value + if (user != null) { + startChat(user) + } + hideView(close) + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.migrate_to_device_chat_migrated), generalGetString(MR.strings.migrate_to_device_finalize_migration)) + } catch (e: Exception) { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error_starting_chat), e.stackTraceToString()) + } + MigrationToDeviceState.save(null) +} + +private fun hideView(close: () -> Unit) { + appPreferences.onboardingStage.set(OnboardingStage.OnboardingComplete) + chatModel.migrationState.value = null + close() +} + +private suspend fun MutableState<MigrationToState?>.cleanUpOnBack(chatReceiver: MigrationToChatReceiver?) { + val state = state + if (state is MigrationToState.ArchiveImportFailed) { + // Original database is not exist, nothing is set up correctly for showing to a user yet. Return to clean state + deleteChatDatabaseFilesAndState() + initChatControllerAndRunMigrations() + } else if (state is MigrationToState.DownloadProgress && state.ctrl != null) { + stopArchiveDownloading(state.fileId, state.ctrl) + } + chatReceiver?.stopAndCleanUp() + getMigrationTempFilesDirectory().deleteRecursively() + MigrationToDeviceState.save(null) + chatModel.migrationState.value = null +} + +private fun strHasSimplexFileLink(text: String): Boolean = + text.startsWith("simplex:/file") || text.startsWith("https://simplex.chat/file") + +private fun fileForTemporaryDatabase(): File = + File(getMigrationTempFilesDirectory(), generateNewFileName("migration", "db", getMigrationTempFilesDirectory())) + +private fun archivePath(): String { + val archiveTime = Clock.System.now() + val ts = SimpleDateFormat("yyyy-MM-dd'T'HHmmss", Locale.US).format(Date.from(archiveTime.toJavaInstant())) + val archiveName = "simplex-chat.$ts.zip" + val archivePath = File(getMigrationTempFilesDirectory(), archiveName) + return archivePath.absolutePath +} + +private class MigrationToChatReceiver( + val ctrl: ChatCtrl, + val databaseUrl: File, + var receiveMessages: Boolean = true, + val processReceivedMsg: suspend (CR) -> Unit +) { + fun start() { + Log.d(TAG, "MigrationChatReceiver startReceiver") + CoroutineScope(Dispatchers.IO).launch { + while (receiveMessages) { + try { + val msg = ChatController.recvMsg(ctrl) + if (msg != null && receiveMessages) { + val r = msg.resp + val rhId = msg.remoteHostId + Log.d(TAG, "processReceivedMsg: ${r.responseType}") + chatModel.addTerminalItem(TerminalItem.resp(rhId, r)) + val finishedWithoutTimeout = withTimeoutOrNull(60_000L) { + processReceivedMsg(r) + } + if (finishedWithoutTimeout == null) { + Log.e(TAG, "Timeout reached while processing received message: " + msg.resp.responseType) + if (appPreferences.developerTools.get() && appPreferences.showSlowApiCalls.get()) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.possible_slow_function_title), + text = generalGetString(MR.strings.possible_slow_function_desc).format(60, msg.resp.responseType + "\n" + Exception().stackTraceToString()), + shareText = true + ) + } + } + } + } catch (e: Exception) { + Log.e(TAG, "MigrationChatReceiver recvMsg/processReceivedMsg exception: " + e.stackTraceToString()) + } catch (e: Exception) { + Log.e(TAG, "MigrationChatReceiver recvMsg/processReceivedMsg throwable: " + e.stackTraceToString()) + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), e.stackTraceToString()) + } + } + } + } + + fun stopAndCleanUp() { + Log.d(TAG, "MigrationChatReceiver.stop") + receiveMessages = false + chatCloseStore(ctrl) + File(databaseUrl.absolutePath + "_chat.db").delete() + File(databaseUrl.absolutePath + "_agent.db").delete() + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt index c2523c9b2e..da59050a3a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddContactLearnMore.kt @@ -1,10 +1,11 @@ package chat.simplex.common.views.newchat -import androidx.compose.foundation.* -import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.padding import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.platform.chatModel +import chat.simplex.common.ui.theme.DEFAULT_PADDING import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.views.helpers.AppBarTitle import chat.simplex.common.views.helpers.KeyChangeEffect @@ -14,10 +15,10 @@ import chat.simplex.res.MR @Composable fun AddContactLearnMore(close: () -> Unit) { - Column( - Modifier.verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.padding(horizontal = DEFAULT_PADDING), ) { - AppBarTitle(stringResource(MR.strings.one_time_link)) + AppBarTitle(stringResource(MR.strings.one_time_link), withPadding = false) ReadableText(MR.strings.scan_qr_to_connect_to_contact) ReadableText(MR.strings.if_you_cant_meet_in_person) ReadableTextWithLink(MR.strings.read_more_in_user_guide_with_link, "https://simplex.chat/docs/guide/readme.html#connect-to-friends") diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt index ee03028909..a7bb026f1a 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/AddGroupView.kt @@ -94,10 +94,9 @@ fun AddGroupLayout( sheetShape = RoundedCornerShape(topStart = 18.dp, topEnd = 18.dp) ) { ModalView(close = close) { - Column( + ColumnWithScrollBar( Modifier .fillMaxSize() - .verticalScroll(rememberScrollState()) .padding(horizontal = DEFAULT_PADDING) ) { AppBarTitle(stringResource(MR.strings.create_secret_group_title), hostDevice(rhId)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt index 6202d8263f..31623e4a61 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/ContactConnectionInfoView.kt @@ -65,14 +65,7 @@ fun ContactConnectionInfoView( share = { if (connReqInvitation != null) clipboard.shareText(connReqInvitation) }, learnMore = { ModalManager.end.showModalCloseable { close -> - Column( - Modifier - .fillMaxHeight() - .padding(horizontal = DEFAULT_PADDING), - verticalArrangement = Arrangement.SpaceBetween - ) { - AddContactLearnMore(close) - } + AddContactLearnMore(close) } } ) @@ -111,9 +104,8 @@ private fun ContactConnectionInfoLayout( } } - Column( - Modifier - .verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier, ) { AppBarTitle( stringResource( diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt index 3b4bb86e66..f341d59305 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/newchat/NewChatView.kt @@ -141,10 +141,10 @@ fun ModalData.NewChatView(rh: RemoteHostInfo?, selection: NewChatOption, showQRC } HorizontalPager(state = pagerState, Modifier.fillMaxSize(), verticalAlignment = Alignment.Top) { index -> - Column( + // LALAL SCROLLBAR DOESN'T WORK + ColumnWithScrollBar( Modifier - .fillMaxSize() - .verticalScroll(rememberScrollState()), + .fillMaxSize(), verticalArrangement = if (index == NewChatOption.INVITE.ordinal && connReqInvitation.isEmpty()) Arrangement.Center else Arrangement.Top) { Spacer(Modifier.height(DEFAULT_PADDING)) when (index) { @@ -232,14 +232,7 @@ private fun AddContactLearnMoreButton() { { if (appPlatform.isDesktop) ModalManager.end.closeModals() ModalManager.end.showModalCloseable { close -> - Column( - Modifier - .fillMaxHeight() - .padding(horizontal = DEFAULT_PADDING), - verticalArrangement = Arrangement.SpaceBetween - ) { - AddContactLearnMore(close) - } + AddContactLearnMore(close) } } ) { @@ -301,7 +294,7 @@ private fun PasteLinkView(rhId: Long?, pastedLink: MutableState<String>, showQRC } @Composable -private fun LinkTextView(link: String, share: Boolean) { +fun LinkTextView(link: String, share: Boolean) { val clipboard = LocalClipboardManager.current Row(Modifier.fillMaxWidth().heightIn(min = 46.dp).padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically) { Box(Modifier.weight(1f).clickable { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt index 190b962eae..5e50475951 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/CreateSimpleXAddress.kt @@ -75,8 +75,8 @@ private fun CreateSimpleXAddressLayout( createAddress: () -> Unit, nextStep: () -> Unit, ) { - Column( - Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(top = DEFAULT_PADDING), + ColumnWithScrollBar( + Modifier.fillMaxSize().padding(top = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally, ) { AppBarTitle(stringResource(MR.strings.simplex_address)) @@ -169,7 +169,8 @@ private fun ProgressIndicator() { } private fun prepareChatBeforeAddressCreation(rhId: Long?) { - if (chatModel.users.isNotEmpty()) return + // No visible users but may have hidden. In this case chat should be started anyway because it's stopped on this stage with hidden users + if (chatModel.users.any { u -> !u.user.hidden }) return withBGApi { val user = chatModel.controller.apiGetActiveUser(rhId) ?: return@withBGApi chatModel.currentUser.value = user diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt index 14b36d0718..28fa1d3a48 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/HowItWorks.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.item.MarkdownText import chat.simplex.common.views.helpers.* @@ -22,7 +23,7 @@ import dev.icerock.moko.resources.StringResource @Composable fun HowItWorks(user: User?, onboardingStage: SharedPreference<OnboardingStage>? = null) { - Column(Modifier + ColumnWithScrollBar(Modifier .fillMaxWidth() .padding(horizontal = DEFAULT_PADDING), ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt index aa413016d1..9124808959 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetNotificationsMode.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.ChatModel import chat.simplex.common.model.NotificationsMode +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.usersettings.changeNotificationsMode @@ -23,10 +24,9 @@ import dev.icerock.moko.resources.StringResource @Composable fun SetNotificationsMode(m: ChatModel) { - Column( + ColumnWithScrollBar( modifier = Modifier .fillMaxSize() - .verticalScroll(rememberScrollState()) .padding(vertical = 14.dp) ) { //CloseSheetBar(null) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt index 9ae34eb180..65bd89b11b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SetupDatabasePassphrase.kt @@ -58,7 +58,7 @@ fun SetupDatabasePassphrase(m: ChatModel) { prefs.storeDBPassphrase.set(false) val newKeyValue = newKey.value - val success = encryptDatabase(currentKey, newKey, confirmNewKey, mutableStateOf(true), saveInPreferences, mutableStateOf(true), progressIndicator) + val success = encryptDatabase(currentKey, newKey, confirmNewKey, mutableStateOf(true), saveInPreferences, mutableStateOf(true), progressIndicator, false) if (success) { startChat(newKeyValue) nextStep() @@ -98,8 +98,8 @@ private fun SetupDatabasePassphraseLayout( onConfirmEncrypt: () -> Unit, nextStep: () -> Unit, ) { - Column( - Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(top = DEFAULT_PADDING), + ColumnWithScrollBar( + Modifier.fillMaxSize().padding(top = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally, ) { AppBarTitle(stringResource(MR.strings.setup_database_passphrase)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt index 2aad2556af..84c48bbc1b 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/SimpleXInfo.kt @@ -5,7 +5,7 @@ import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* -import androidx.compose.runtime.Composable +import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color @@ -16,8 +16,12 @@ import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.* import chat.simplex.common.model.* +import chat.simplex.common.platform.ColumnWithScrollBar +import chat.simplex.common.platform.chatModel import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.migration.MigrateToDeviceView +import chat.simplex.common.views.migration.MigrationToState import chat.simplex.res.MR import dev.icerock.moko.resources.StringResource @@ -36,10 +40,9 @@ fun SimpleXInfoLayout( onboardingStage: SharedPreference<OnboardingStage>?, showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), ) { - Column( + ColumnWithScrollBar( Modifier .fillMaxSize() - .verticalScroll(rememberScrollState()) .padding(start = DEFAULT_PADDING , end = DEFAULT_PADDING, top = DEFAULT_PADDING), horizontalAlignment = Alignment.CenterHorizontally ) { @@ -62,17 +65,33 @@ fun SimpleXInfoLayout( OnboardingActionButton(user, onboardingStage) } Spacer(Modifier.fillMaxHeight().weight(1f)) + + Box( + Modifier + .fillMaxWidth() + .padding(top = DEFAULT_PADDING), contentAlignment = Alignment.Center + ) { + SimpleButtonDecorated(text = stringResource(MR.strings.migrate_from_another_device), icon = painterResource(MR.images.ic_download), + click = { + chatModel.migrationState.value = MigrationToState.PasteOrScanLink + ModalManager.fullscreen.showCustomModal { close -> MigrateToDeviceView(close) } }) + } } Box( Modifier .fillMaxWidth() - .padding(bottom = DEFAULT_PADDING.times(1.5f), top = DEFAULT_PADDING), contentAlignment = Alignment.Center + .padding(bottom = DEFAULT_PADDING.times(1.5f), top = if (onboardingStage == null) DEFAULT_PADDING else 0.dp), contentAlignment = Alignment.Center ) { SimpleButtonDecorated(text = stringResource(MR.strings.how_it_works), icon = painterResource(MR.images.ic_info), click = showModal { HowItWorks(user, onboardingStage) }) } } + LaunchedEffect(Unit) { + if (chatModel.migrationState.value != null && !ModalManager.fullscreen.hasModalsOpen()) { + ModalManager.fullscreen.showCustomModal(animated = false) { close -> MigrateToDeviceView(close) } + } + } } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt index 38805472f2..3f63e72a69 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/onboarding/WhatsNewView.kt @@ -18,6 +18,7 @@ import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @@ -105,11 +106,10 @@ fun WhatsNewView(viaSettings: Boolean = false, close: () -> Unit) { val v = versionDescriptions[currentVersion.value] ModalView(close = close) { - Column( + ColumnWithScrollBar( Modifier .fillMaxSize() - .padding(horizontal = DEFAULT_PADDING) - .verticalScroll(rememberScrollState()), + .padding(horizontal = DEFAULT_PADDING), verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING.times(0.75f)) ) { AppBarTitle(String.format(generalGetString(MR.strings.new_in_version), v.version), bottomPadding = DEFAULT_PADDING) @@ -495,6 +495,32 @@ private val versionDescriptions: List<VersionDescription> = listOf( ) ) ), + VersionDescription( + version = "v5.6", + post = "https://simplex.chat/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.html", + features = listOf( + FeatureDescription( + icon = MR.images.ic_vpn_key_filled, + titleId = MR.strings.v5_6_quantum_resistant_encryption, + descrId = MR.strings.v5_6_quantum_resistant_encryption_descr + ), + FeatureDescription( + icon = MR.images.ic_ios_share, + titleId = MR.strings.v5_6_app_data_migration, + descrId = MR.strings.v5_6_app_data_migration_descr + ), + FeatureDescription( + icon = MR.images.ic_call, + titleId = MR.strings.v5_6_picture_in_picture_calls, + descrId = MR.strings.v5_6_picture_in_picture_calls_descr + ), + FeatureDescription( + icon = MR.images.ic_back_hand, + titleId = MR.strings.v5_6_safer_groups, + descrId = MR.strings.v5_6_safer_groups_descr + ) + ) + ), ) private val lastVersion = versionDescriptions.last().version diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt index 5acb240cb3..76f522c614 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectDesktopView.kt @@ -75,8 +75,8 @@ private fun ConnectDesktopLayout(deviceName: String, close: () -> Unit) { val sessionAddress = remember { mutableStateOf("") } val remoteCtrls = remember { mutableStateListOf<RemoteCtrlInfo>() } val session = remember { chatModel.remoteCtrlSession }.value - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), ) { val discovery = if (session == null) null else session.sessionState is UIRemoteCtrlSessionState.Searching if (discovery == true || (discovery == null && !showConnectScreen.value)) { @@ -391,8 +391,8 @@ private fun DesktopAddressView(sessionAddress: MutableState<String>) { @Composable private fun LinkedDesktopsView(remoteCtrls: SnapshotStateList<RemoteCtrlInfo>) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.linked_desktops)) SectionView(stringResource(MR.strings.desktop_devices).uppercase()) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt index bf7884e9d8..bd1be525be 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/remote/ConnectMobileView.kt @@ -91,8 +91,8 @@ fun ConnectMobileLayout( connectDesktop: () -> Unit, deleteHost: (RemoteHostInfo) -> Unit, ) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp) ) { AppBarTitle(stringResource(if (remember { chatModel.remoteHosts }.isEmpty()) MR.strings.link_a_mobile else MR.strings.linked_mobiles)) @@ -181,8 +181,8 @@ private fun ConnectMobileViewLayout( refreshQrCode: () -> Unit = {}, UnderQrLayout: @Composable () -> Unit = {}, ) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp) ) { if (title != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt index 7683f0ed18..a2ea5959c3 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/AdvancedNetworkSettings.kt @@ -23,6 +23,7 @@ import chat.simplex.common.model.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.res.MR import java.text.DecimalFormat @@ -141,10 +142,9 @@ fun AdvancedNetworkSettingsView(chatModel: ChatModel) { ) { val secondsLabel = stringResource(MR.strings.network_option_seconds_label) - Column( + ColumnWithScrollBar( Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()), + .fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.network_settings_title)) SectionView { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt index 62216761ed..67f9a02ca7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt @@ -57,8 +57,8 @@ object AppearanceScope { @Composable fun CustomizeThemeView(editColor: (ThemeColor, Color) -> Unit) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), ) { val currentTheme by CurrentColors.collectAsState() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt index d4219a51c5..94415b0ee0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/CallSettings.kt @@ -16,6 +16,7 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import chat.simplex.common.views.helpers.* import chat.simplex.common.model.* +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.res.MR @Composable @@ -35,8 +36,8 @@ fun CallSettingsLayout( callOnLockScreen: SharedPreference<CallOnLockScreen>, editIceServers: () -> Unit, ) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp) ) { AppBarTitle(stringResource(MR.strings.your_calls)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt index cc268e9a9d..a03a015207 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/DeveloperView.kt @@ -12,10 +12,9 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalUriHandler import chat.simplex.common.model.* +import chat.simplex.common.platform.* import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import chat.simplex.common.platform.appPlatform -import chat.simplex.common.platform.appPreferences import chat.simplex.common.views.TerminalView import chat.simplex.common.views.helpers.* import chat.simplex.res.MR @@ -26,7 +25,7 @@ fun DeveloperView( showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), withAuth: (title: String, desc: String, block: () -> Unit) -> Unit ) { - Column(Modifier.fillMaxWidth().verticalScroll(rememberScrollState())) { + ColumnWithScrollBar(Modifier.fillMaxWidth()) { val uriHandler = LocalUriHandler.current AppBarTitle(stringResource(MR.strings.settings_developer_tools)) val developerTools = m.controller.appPrefs.developerTools @@ -58,6 +57,14 @@ fun DeveloperView( SettingsPreferenceItem(painterResource(MR.images.ic_report), stringResource(MR.strings.show_internal_errors), appPreferences.showInternalErrors) SettingsPreferenceItem(painterResource(MR.images.ic_avg_pace), stringResource(MR.strings.show_slow_api_calls), appPreferences.showSlowApiCalls) } + + SectionSpacer() + SectionView("Experimental".uppercase()) { + SettingsPreferenceItem(painterResource(MR.images.ic_vpn_key_filled), "Post-quantum E2EE", m.controller.appPrefs.pqExperimentalEnabled, onChange = { enable -> + withBGApi { m.controller.apiSetPQEncryption(enable) } + }) + SectionTextFooter("In this version applies only to new contacts.") + } } SectionBottomSpacer() } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HelpView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HelpView.kt index 33e183aaaf..c2bf69bc0e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HelpView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HelpView.kt @@ -7,6 +7,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource import androidx.compose.desktop.ui.tooling.preview.Preview +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.ui.theme.SimpleXTheme import chat.simplex.common.views.chatlist.ChatHelpView @@ -20,10 +21,9 @@ fun HelpView(userDisplayName: String) { @Composable fun HelpLayout(userDisplayName: String) { - Column( + ColumnWithScrollBar( Modifier .fillMaxWidth() - .verticalScroll(rememberScrollState()) .padding(horizontal = DEFAULT_PADDING), ){ AppBarTitle(String.format(stringResource(MR.strings.personal_welcome), userDisplayName), withPadding = false) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt index f3496c850f..4dd406c398 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/HiddenProfileView.kt @@ -17,6 +17,7 @@ import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp import chat.simplex.common.model.ChatModel import chat.simplex.common.model.User +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chatlist.UserProfileRow import chat.simplex.common.views.database.PassphraseField @@ -53,10 +54,9 @@ private fun HiddenProfileLayout( user: User, saveProfilePassword: (String) -> Unit ) { - Column( + ColumnWithScrollBar( Modifier - .fillMaxWidth() - .verticalScroll(rememberScrollState()), + .fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.hide_profile)) SectionView(padding = PaddingValues(start = 8.dp, end = DEFAULT_PADDING)) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncognitoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncognitoView.kt index 6da0d34bd3..6098f66941 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncognitoView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/IncognitoView.kt @@ -1,13 +1,13 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.ui.theme.DEFAULT_PADDING import chat.simplex.common.views.helpers.AppBarTitle import chat.simplex.common.views.helpers.generalGetString @@ -21,11 +21,10 @@ fun IncognitoView() { @Composable fun IncognitoLayout() { - Column { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.settings_section_title_incognito)) Column( Modifier - .verticalScroll(rememberScrollState()) .padding(horizontal = DEFAULT_PADDING), verticalArrangement = Arrangement.spacedBy(20.dp) ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt index 66b4a0e839..e1f050b0b6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NetworkAndServers.kt @@ -25,6 +25,7 @@ import androidx.compose.ui.platform.LocalDensity import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.platform.chatModel import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.item.ClickableText @@ -33,12 +34,7 @@ import chat.simplex.common.views.helpers.annotatedStringResource import chat.simplex.res.MR @Composable -fun NetworkAndServersView( - chatModel: ChatModel, - showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), -) { +fun NetworkAndServersView() { val currentRemoteHost by remember { chatModel.currentRemoteHost } // It's not a state, just a one-time value. Shouldn't be used in any state-related situations val netCfg = remember { chatModel.controller.getNetCfg() } @@ -55,9 +51,6 @@ fun NetworkAndServersView( onionHosts = onionHosts, sessionMode = sessionMode, proxyPort = proxyPort, - showModal = showModal, - showSettingsModal = showSettingsModal, - showCustomModal = showCustomModal, toggleSocksProxy = { enable -> if (enable) { AlertManager.shared.showAlertDialog( @@ -154,31 +147,30 @@ fun NetworkAndServersView( onionHosts: MutableState<OnionHosts>, sessionMode: MutableState<TransportSessionMode>, proxyPort: State<Int>, - showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - showCustomModal: (@Composable ModalData.(ChatModel, () -> Unit) -> Unit) -> (() -> Unit), toggleSocksProxy: (Boolean) -> Unit, useOnion: (OnionHosts) -> Unit, updateSessionMode: (TransportSessionMode) -> Unit, ) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + val m = chatModel + ColumnWithScrollBar( + Modifier.fillMaxWidth(), verticalArrangement = Arrangement.spacedBy(8.dp) ) { AppBarTitle(stringResource(MR.strings.network_and_servers)) if (!chatModel.desktopNoUserNoRemote) { SectionView(generalGetString(MR.strings.settings_section_title_messages)) { - SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.smp_servers), showCustomModal { m, close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.SMP, close) }) + SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.smp_servers), { ModalManager.start.showCustomModal { close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.SMP, close) } }) - SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.xftp_servers), showCustomModal { m, close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.XFTP, close) }) + SettingsActionItem(painterResource(MR.images.ic_dns), stringResource(MR.strings.xftp_servers), { ModalManager.start.showCustomModal { close -> ProtocolServersView(m, m.remoteHostId, ServerProtocol.XFTP, close) } }) if (currentRemoteHost == null) { - UseSocksProxySwitch(networkUseSocksProxy, proxyPort, toggleSocksProxy, showSettingsModal) - UseOnionHosts(onionHosts, networkUseSocksProxy, showSettingsModal, useOnion) + val showModal = { it: @Composable ModalData.() -> Unit -> ModalManager.start.showModal(content = it) } + UseSocksProxySwitch(networkUseSocksProxy, proxyPort, toggleSocksProxy, showModal, chatModel.controller.appPrefs.networkProxyHostPort, false) + UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion) if (developerTools) { - SessionModePicker(sessionMode, showSettingsModal, updateSessionMode) + SessionModePicker(sessionMode, showModal, updateSessionMode) } - SettingsActionItem(painterResource(MR.images.ic_cable), stringResource(MR.strings.network_settings), showSettingsModal { AdvancedNetworkSettingsView(it) }) + SettingsActionItem(painterResource(MR.images.ic_cable), stringResource(MR.strings.network_settings), { ModalManager.start.showModal { AdvancedNetworkSettingsView(m) } }) } } } @@ -196,18 +188,39 @@ fun NetworkAndServersView( } SectionView(generalGetString(MR.strings.settings_section_title_calls)) { - SettingsActionItem(painterResource(MR.images.ic_electrical_services), stringResource(MR.strings.webrtc_ice_servers), showModal { RTCServersView(it) }) + SettingsActionItem(painterResource(MR.images.ic_electrical_services), stringResource(MR.strings.webrtc_ice_servers), { ModalManager.start.showModal { RTCServersView(m) } }) } SectionBottomSpacer() } } +@Composable fun OnionRelatedLayout( + developerTools: Boolean, + networkUseSocksProxy: MutableState<Boolean>, + onionHosts: MutableState<OnionHosts>, + sessionMode: MutableState<TransportSessionMode>, + networkProxyHostPort: SharedPreference<String?>, + proxyPort: State<Int>, + toggleSocksProxy: (Boolean) -> Unit, + useOnion: (OnionHosts) -> Unit, + updateSessionMode: (TransportSessionMode) -> Unit, +) { + val showModal = { it: @Composable ModalData.() -> Unit -> ModalManager.fullscreen.showModal(content = it) } + UseSocksProxySwitch(networkUseSocksProxy, proxyPort, toggleSocksProxy, showModal, networkProxyHostPort, true) + UseOnionHosts(onionHosts, networkUseSocksProxy, showModal, useOnion) + if (developerTools) { + SessionModePicker(sessionMode, showModal, updateSessionMode) + } +} + @Composable fun UseSocksProxySwitch( networkUseSocksProxy: MutableState<Boolean>, proxyPort: State<Int>, toggleSocksProxy: (Boolean) -> Unit, - showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit) + showModal: (@Composable ModalData.() -> Unit) -> Unit, + networkProxyHostPort: SharedPreference<String?> = chatModel.controller.appPrefs.networkProxyHostPort, + migration: Boolean = false, ) { Row( Modifier.fillMaxWidth().padding(end = DEFAULT_PADDING), @@ -227,8 +240,11 @@ fun UseSocksProxySwitch( val text = buildAnnotatedString { append(generalGetString(MR.strings.network_socks_toggle_use_socks_proxy) + " (") val style = SpanStyle(color = MaterialTheme.colors.primary) + val disabledStyle = SpanStyle(color = MaterialTheme.colors.onBackground) withAnnotation(tag = "PORT", annotation = generalGetString(MR.strings.network_proxy_port).format(proxyPort.value)) { - withStyle(style) { append(generalGetString(MR.strings.network_proxy_port).format(proxyPort.value)) } + withStyle(if (networkUseSocksProxy.value || !migration) style else disabledStyle) { + append(generalGetString(MR.strings.network_proxy_port).format(proxyPort.value)) + } } append(")") } @@ -238,7 +254,9 @@ fun UseSocksProxySwitch( onClick = { offset -> text.getStringAnnotations(tag = "PORT", start = offset, end = offset) .firstOrNull()?.let { _ -> - showSettingsModal { SockProxySettings(it) }() + if (networkUseSocksProxy.value || !migration) { + showModal { SockProxySettings(chatModel, networkProxyHostPort, migration) } + } } }, shouldConsumeEvent = { offset -> @@ -254,25 +272,28 @@ fun UseSocksProxySwitch( } @Composable -fun SockProxySettings(m: ChatModel) { - Column( +fun SockProxySettings( + m: ChatModel, + networkProxyHostPort: SharedPreference<String?> = m.controller.appPrefs.networkProxyHostPort, + migration: Boolean, +) { + ColumnWithScrollBar( Modifier .fillMaxWidth() - .verticalScroll(rememberScrollState()) ) { val defaultHostPort = remember { "localhost:9050" } AppBarTitle(generalGetString(MR.strings.network_socks_proxy_settings)) - val hostPort by remember { m.controller.appPrefs.networkProxyHostPort.state } + val hostPortSaved by remember { networkProxyHostPort.state } val hostUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { - mutableStateOf(TextFieldValue(hostPort?.split(":")?.firstOrNull() ?: "localhost")) + mutableStateOf(TextFieldValue(hostPortSaved?.split(":")?.firstOrNull() ?: "localhost")) } val portUnsaved = rememberSaveable(stateSaver = TextFieldValue.Saver) { - mutableStateOf(TextFieldValue(hostPort?.split(":")?.lastOrNull() ?: "9050")) + mutableStateOf(TextFieldValue(hostPortSaved?.split(":")?.lastOrNull() ?: "9050")) } val save = { withBGApi { - m.controller.appPrefs.networkProxyHostPort.set(hostUnsaved.value.text + ":" + portUnsaved.value.text) - if (m.controller.appPrefs.networkUseSocksProxy.get()) { + networkProxyHostPort.set(hostUnsaved.value.text + ":" + portUnsaved.value.text) + if (m.controller.appPrefs.networkUseSocksProxy.get() && !migration) { m.controller.apiSetNetworkConfig(m.controller.getNetCfg()) } } @@ -281,21 +302,21 @@ fun SockProxySettings(m: ChatModel) { SectionItemView { ResetToDefaultsButton({ val reset = { - m.controller.appPrefs.networkProxyHostPort.set(defaultHostPort) + networkProxyHostPort.set(defaultHostPort) val newHost = defaultHostPort.split(":").first() val newPort = defaultHostPort.split(":").last() hostUnsaved.value = hostUnsaved.value.copy(newHost, TextRange(newHost.length)) portUnsaved.value = portUnsaved.value.copy(newPort, TextRange(newPort.length)) save() } - if (m.controller.appPrefs.networkUseSocksProxy.get()) { + if (m.controller.appPrefs.networkUseSocksProxy.get() && !migration) { showUpdateNetworkSettingsDialog { reset() } } else { reset() } - }, disabled = hostPort == defaultHostPort) + }, disabled = hostPortSaved == defaultHostPort) } SectionItemView { DefaultConfigurableTextField( @@ -321,14 +342,14 @@ fun SockProxySettings(m: ChatModel) { SectionCustomFooter { NetworkSectionFooter( revert = { - val prevHost = m.controller.appPrefs.networkProxyHostPort.get()?.split(":")?.firstOrNull() ?: "localhost" - val prevPort = m.controller.appPrefs.networkProxyHostPort.get()?.split(":")?.lastOrNull() ?: "9050" + val prevHost = hostPortSaved?.split(":")?.firstOrNull() ?: "localhost" + val prevPort = hostPortSaved?.split(":")?.lastOrNull() ?: "9050" hostUnsaved.value = hostUnsaved.value.copy(prevHost, TextRange(prevHost.length)) portUnsaved.value = portUnsaved.value.copy(prevPort, TextRange(prevPort.length)) }, - save = { if (m.controller.appPrefs.networkUseSocksProxy.get()) showUpdateNetworkSettingsDialog { save() } else save() }, - revertDisabled = hostPort == (hostUnsaved.value.text + ":" + portUnsaved.value.text), - saveDisabled = hostPort == (hostUnsaved.value.text + ":" + portUnsaved.value.text) || + save = { if (m.controller.appPrefs.networkUseSocksProxy.get() && !migration) showUpdateNetworkSettingsDialog { save() } else save() }, + revertDisabled = hostPortSaved == (hostUnsaved.value.text + ":" + portUnsaved.value.text), + saveDisabled = hostPortSaved == (hostUnsaved.value.text + ":" + portUnsaved.value.text) || remember { derivedStateOf { !validHost(hostUnsaved.value.text) } }.value || remember { derivedStateOf { !validPort(portUnsaved.value.text) } }.value ) @@ -341,7 +362,7 @@ fun SockProxySettings(m: ChatModel) { private fun UseOnionHosts( onionHosts: MutableState<OnionHosts>, enabled: State<Boolean>, - showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), + showModal: (@Composable ModalData.() -> Unit) -> Unit, useOnion: (OnionHosts) -> Unit, ) { val values = remember { @@ -353,29 +374,43 @@ private fun UseOnionHosts( } } } - val onSelected = showModal { - Column( - Modifier.fillMaxWidth(), - ) { - AppBarTitle(stringResource(MR.strings.network_use_onion_hosts)) - SectionViewSelectable(null, onionHosts, values, useOnion) + val onSelected = { + showModal { + ColumnWithScrollBar( + Modifier.fillMaxWidth(), + ) { + AppBarTitle(stringResource(MR.strings.network_use_onion_hosts)) + SectionViewSelectable(null, onionHosts, values, useOnion) + } } } - SectionItemWithValue( - generalGetString(MR.strings.network_use_onion_hosts), - onionHosts, - values, - icon = painterResource(MR.images.ic_security), - enabled = enabled, - onSelected = onSelected - ) + if (enabled.value) { + SectionItemWithValue( + generalGetString(MR.strings.network_use_onion_hosts), + onionHosts, + values, + icon = painterResource(MR.images.ic_security), + enabled = enabled, + onSelected = onSelected + ) + } else { + // In reality, when socks proxy is disabled, this option acts like NEVER regardless of what was chosen before + SectionItemWithValue( + generalGetString(MR.strings.network_use_onion_hosts), + remember { mutableStateOf(OnionHosts.NEVER) }, + listOf(ValueTitleDesc(OnionHosts.NEVER, generalGetString(MR.strings.network_use_onion_hosts_no), AnnotatedString(generalGetString(MR.strings.network_use_onion_hosts_no_desc)))), + icon = painterResource(MR.images.ic_security), + enabled = enabled, + onSelected = {} + ) + } } @Composable private fun SessionModePicker( sessionMode: MutableState<TransportSessionMode>, - showModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), + showModal: (@Composable ModalData.() -> Unit) -> Unit, updateSessionMode: (TransportSessionMode) -> Unit, ) { val density = LocalDensity.current @@ -393,12 +428,14 @@ private fun SessionModePicker( sessionMode, values, icon = painterResource(MR.images.ic_safety_divider), - onSelected = showModal { - Column( - Modifier.fillMaxWidth(), - ) { - AppBarTitle(stringResource(MR.strings.network_session_mode_transport_isolation)) - SectionViewSelectable(null, sessionMode, values, updateSessionMode) + onSelected = { + showModal { + ColumnWithScrollBar( + Modifier.fillMaxWidth(), + ) { + AppBarTitle(stringResource(MR.strings.network_session_mode_transport_isolation)) + SectionViewSelectable(null, sessionMode, values, updateSessionMode) + } } } ) @@ -455,9 +492,6 @@ fun PreviewNetworkAndServersLayout() { developerTools = true, networkUseSocksProxy = remember { mutableStateOf(true) }, proxyPort = remember { mutableStateOf(9050) }, - showModal = { {} }, - showSettingsModal = { {} }, - showCustomModal = { {} }, toggleSocksProxy = {}, onionHosts = remember { mutableStateOf(OnionHosts.PREFER) }, sessionMode = remember { mutableStateOf(TransportSessionMode.User) }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt index ddd1b4068e..515d73a426 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/NotificationsSettingsView.kt @@ -56,8 +56,8 @@ fun NotificationsSettingsLayout( val modes = remember { notificationModes() } val previewModes = remember { notificationPreviewModes() } - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.notifications)) SectionView(null) { @@ -90,7 +90,7 @@ fun NotificationsModeView( onNotificationsModeSelected: (NotificationsMode) -> Unit, ) { val modes = remember { notificationModes() } - Column( + ColumnWithScrollBar( Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.settings_notifications_mode_title).lowercase().capitalize(Locale.current)) @@ -104,7 +104,7 @@ fun NotificationPreviewView( onNotificationPreviewModeSelected: (NotificationPreviewMode) -> Unit, ) { val previewModes = remember { notificationPreviewModes() } - Column( + ColumnWithScrollBar( Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.settings_notification_preview_title)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt index 8b7ac68205..cd0e40f5d0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Preferences.kt @@ -15,6 +15,7 @@ import androidx.compose.ui.Modifier import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.views.helpers.* import chat.simplex.common.model.* +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.res.MR @Composable @@ -62,8 +63,8 @@ private fun PreferencesLayout( reset: () -> Unit, savePrefs: () -> Unit, ) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.your_preferences)) val timedMessages = remember(preferences) { mutableStateOf(preferences.timedMessages.allow) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt index 5f4b6c01e3..b376285259 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/PrivacySettings.kt @@ -30,8 +30,7 @@ import chat.simplex.common.views.isValidDisplayName import chat.simplex.common.views.localauth.SetAppPasscodeView import chat.simplex.common.views.onboarding.ReadableText import chat.simplex.common.model.ChatModel -import chat.simplex.common.platform.AppPlatform -import chat.simplex.common.platform.appPlatform +import chat.simplex.common.platform.* enum class LAMode { SYSTEM, @@ -55,8 +54,8 @@ fun PrivacySettingsView( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), setPerformLA: (Boolean) -> Unit ) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), ) { val simplexLinkMode = chatModel.controller.appPrefs.simplexLinkMode AppBarTitle(stringResource(MR.strings.your_privacy)) @@ -473,8 +472,8 @@ fun SimplexLockView( } } - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.chat_lock)) SectionView { @@ -580,8 +579,8 @@ fun SimplexLockView( @Composable private fun SelfDestructInfoView() { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()).padding(horizontal = DEFAULT_PADDING), + ColumnWithScrollBar( + Modifier.fillMaxWidth().padding(horizontal = DEFAULT_PADDING), ) { AppBarTitle(stringResource(MR.strings.self_destruct), withPadding = false) ReadableText(stringResource(MR.strings.if_you_enter_self_destruct_code)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt index cb3b16b227..399895870f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServerView.kt @@ -5,7 +5,6 @@ import SectionDividerSpaced import SectionItemView import SectionItemViewSpaceBetween import SectionView -import chat.simplex.common.platform.Log import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.text.selection.SelectionContainer @@ -20,13 +19,13 @@ import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.common.platform.TAG import chat.simplex.common.model.* import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.views.newchat.QRCode import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.* import chat.simplex.res.MR import kotlinx.coroutines.* import kotlinx.coroutines.flow.distinctUntilChanged @@ -76,10 +75,9 @@ private fun ProtocolServerLayout( onUpdate: (ServerCfg) -> Unit, onDelete: () -> Unit, ) { - Column( + ColumnWithScrollBar( Modifier .fillMaxWidth() - .verticalScroll(rememberScrollState()) ) { AppBarTitle(stringResource(if (server.preset) MR.strings.smp_servers_preset_server else MR.strings.smp_servers_your_server)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt index ad1648f1ea..fe847432fb 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/ProtocolServersView.kt @@ -22,6 +22,7 @@ import androidx.compose.ui.unit.dp import chat.simplex.common.model.ServerAddress.Companion.parseServerAddress import chat.simplex.common.views.helpers.* import chat.simplex.common.model.* +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.platform.appPlatform import chat.simplex.res.MR @@ -191,10 +192,9 @@ private fun ProtocolServersLayout( saveSMPServers: () -> Unit, showServer: (ServerCfg) -> Unit, ) { - Column( + ColumnWithScrollBar( Modifier .fillMaxWidth() - .verticalScroll(rememberScrollState()) ) { AppBarTitle(stringResource(if (serverProtocol == ServerProtocol.SMP) MR.strings.your_SMP_servers else MR.strings.your_XFTP_servers)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt index 50bed458cc..761a74d6e4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/RTCServers.kt @@ -19,6 +19,7 @@ import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.ChatModel +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.parseRTCIceServers import chat.simplex.common.views.helpers.* @@ -98,12 +99,11 @@ fun RTCServersLayout( saveRTCServers: () -> Unit, editOn: () -> Unit, ) { - Column { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.your_ICE_servers)) Column( Modifier .fillMaxWidth() - .verticalScroll(rememberScrollState()) .padding(horizontal = DEFAULT_PADDING), verticalArrangement = Arrangement.spacedBy(8.dp) ) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt index c18fd3f6f6..f0960c1511 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SetDeliveryReceiptsView.kt @@ -74,8 +74,10 @@ private fun SetDeliveryReceiptsLayout( userCount: Int, ) { val endPadding = if (appPlatform.isDesktop) 56.dp else 0.dp + val (scrollBarAlpha, scrollModifier, scrollJob) = platform.desktopScrollBarComponents() + val scrollState = rememberScrollState() Column( - Modifier.fillMaxSize().verticalScroll(rememberScrollState()).padding(top = DEFAULT_PADDING, end = endPadding), + Modifier.fillMaxSize().verticalScroll(scrollState).then(if (appPlatform.isDesktop) scrollModifier else Modifier).padding(top = DEFAULT_PADDING, end = endPadding), horizontalAlignment = Alignment.CenterHorizontally, ) { AppBarTitle(stringResource(MR.strings.delivery_receipts_title)) @@ -95,6 +97,11 @@ private fun SetDeliveryReceiptsLayout( SectionBottomSpacer() } + if (appPlatform.isDesktop) { + Box(Modifier.fillMaxSize().padding(end = endPadding)) { + platform.desktopScrollBar(scrollState, Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false) + } + } } @Composable diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt index d92f2f0f13..298eb39737 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/SettingsView.kt @@ -28,6 +28,7 @@ import chat.simplex.common.ui.theme.* import chat.simplex.common.views.CreateProfile import chat.simplex.common.views.database.DatabaseView import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.migration.MigrateFromDeviceView import chat.simplex.common.views.onboarding.SimpleXInfo import chat.simplex.common.views.onboarding.WhatsNewView import chat.simplex.common.views.remote.ConnectDesktopView @@ -104,10 +105,9 @@ fun SettingsLayout( val theme = CurrentColors.collectAsState() val uriHandler = LocalUriHandler.current Box(Modifier.fillMaxSize()) { - Column( + ColumnWithScrollBar( Modifier .fillMaxSize() - .verticalScroll(rememberScrollState()) .themedBackground(theme.value.base) .padding(top = if (appPlatform.isAndroid) DEFAULT_PADDING else DEFAULT_PADDING * 3) ) { @@ -135,12 +135,13 @@ fun SettingsLayout( } else { SettingsActionItem(painterResource(MR.images.ic_desktop), stringResource(MR.strings.settings_section_title_use_from_desktop), showCustomModal{ it, close -> ConnectDesktopView(close) }, disabled = stopped, extraPadding = true) } + SettingsActionItem(painterResource(MR.images.ic_ios_share), stringResource(MR.strings.migrate_from_device_to_another_device), { withAuth(generalGetString(MR.strings.auth_open_migration_to_another_device), generalGetString(MR.strings.auth_log_in_using_credential)) { ModalManager.fullscreen.showCustomModal { close -> MigrateFromDeviceView(close) } }}, disabled = stopped, extraPadding = true) } SectionDividerSpaced() SectionView(stringResource(MR.strings.settings_section_title_settings)) { SettingsActionItem(painterResource(if (notificationsMode.value == NotificationsMode.OFF) MR.images.ic_bolt_off else MR.images.ic_bolt), stringResource(MR.strings.notifications), showSettingsModal { NotificationsSettingsView(it) }, disabled = stopped, extraPadding = true) - SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showSettingsModal { NetworkAndServersView(it, showModal, showSettingsModal, showCustomModal) }, disabled = stopped, extraPadding = true) + SettingsActionItem(painterResource(MR.images.ic_wifi_tethering), stringResource(MR.strings.network_and_servers), showSettingsModal { NetworkAndServersView() }, disabled = stopped, extraPadding = true) SettingsActionItem(painterResource(MR.images.ic_videocam), stringResource(MR.strings.settings_audio_video_calls), showSettingsModal { CallSettingsView(it, showModal) }, disabled = stopped, extraPadding = true) SettingsActionItem(painterResource(MR.images.ic_lock), stringResource(MR.strings.privacy_and_security), showSettingsModal { PrivacySettingsView(it, showSettingsModal, setPerformLA) }, disabled = stopped, extraPadding = true) SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it, showSettingsModal) }, extraPadding = true) @@ -366,7 +367,7 @@ fun SettingsActionItem(icon: Painter, text: String, click: (() -> Unit)? = null, } @Composable -fun SettingsActionItemWithContent(icon: Painter?, text: String? = null, click: (() -> Unit)? = null, iconColor: Color = MaterialTheme.colors.secondary, disabled: Boolean = false, extraPadding: Boolean = false, content: @Composable RowScope.() -> Unit) { +fun SettingsActionItemWithContent(icon: Painter?, text: String? = null, click: (() -> Unit)? = null, iconColor: Color = MaterialTheme.colors.secondary, textColor: Color = MaterialTheme.colors.onBackground, disabled: Boolean = false, extraPadding: Boolean = false, content: @Composable RowScope.() -> Unit) { SectionItemView( click, extraPadding = extraPadding, @@ -382,7 +383,7 @@ fun SettingsActionItemWithContent(icon: Painter?, text: String? = null, click: ( } if (text != null) { val padding = with(LocalDensity.current) { 6.sp.toDp() } - Text(text, Modifier.weight(1f).padding(vertical = padding), color = if (disabled) MaterialTheme.colors.secondary else MaterialTheme.colors.onBackground) + Text(text, Modifier.weight(1f).padding(vertical = padding), color = if (disabled) MaterialTheme.colors.secondary else textColor) Spacer(Modifier.width(DEFAULT_PADDING)) Row(Modifier.widthIn(max = (windowWidth() - DEFAULT_PADDING * 2) / 2)) { content() diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt index 276c595435..63baece5cf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressLearnMore.kt @@ -1,9 +1,10 @@ package chat.simplex.common.views.usersettings -import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import chat.simplex.common.platform.ColumnWithScrollBar +import chat.simplex.common.ui.theme.DEFAULT_PADDING import dev.icerock.moko.resources.compose.stringResource import chat.simplex.common.views.helpers.* import chat.simplex.common.views.onboarding.ReadableText @@ -12,8 +13,9 @@ import chat.simplex.res.MR @Composable fun UserAddressLearnMore() { - Column( - Modifier.verticalScroll(rememberScrollState()), + ColumnWithScrollBar(Modifier + .fillMaxHeight() + .padding(horizontal = DEFAULT_PADDING) ) { AppBarTitle(stringResource(MR.strings.simplex_address)) ReadableText(MR.strings.you_can_share_your_address) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt index ffaec8eb04..b595bd4e0e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserAddressView.kt @@ -7,9 +7,7 @@ import SectionTextFooter import SectionView import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.verticalScroll import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable @@ -91,14 +89,7 @@ fun UserAddressView( }, learnMore = { ModalManager.start.showModal { - Column( - Modifier - .fillMaxHeight() - .padding(horizontal = DEFAULT_PADDING), - verticalArrangement = Arrangement.SpaceBetween - ) { - UserAddressLearnMore() - } + UserAddressLearnMore() } }, share = { userAddress: String -> clipboard.shareText(userAddress) }, @@ -182,9 +173,7 @@ private fun UserAddressLayout( deleteAddress: () -> Unit, saveAas: (AutoAcceptState, MutableState<AutoAcceptState>) -> Unit, ) { - Column( - Modifier.verticalScroll(rememberScrollState()), - ) { + ColumnWithScrollBar { AppBarTitle(stringResource(MR.strings.simplex_address), hostDevice(user?.remoteHostId), withPadding = false) Column( Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt index 1f5177d08b..e3636ec9c5 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfileView.kt @@ -98,9 +98,8 @@ fun UserProfileLayout( } } ModalView(close = closeWithAlert) { - Column( + ColumnWithScrollBar( Modifier - .verticalScroll(scrollState) .padding(horizontal = DEFAULT_PADDING), ) { AppBarTitle(stringResource(MR.strings.your_current_profile)) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt index 1beccd516c..b40cc7db92 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/UserProfilesView.kt @@ -158,10 +158,9 @@ private fun UserProfilesLayout( unmuteUser: (User) -> Unit, showHiddenProfile: (User) -> Unit, ) { - Column( + ColumnWithScrollBar( Modifier .fillMaxWidth() - .verticalScroll(rememberScrollState()) ) { if (profileHidden.value) { SectionView { @@ -260,10 +259,9 @@ enum class UserProfileAction { @Composable private fun ProfileActionView(action: UserProfileAction, user: User, doAction: (String) -> Unit) { - Column( + ColumnWithScrollBar( Modifier .fillMaxWidth() - .verticalScroll(rememberScrollState()) ) { val actionPassword = rememberSaveable { mutableStateOf("") } val passwordValid by remember { derivedStateOf { actionPassword.value == actionPassword.value.trim() } } @@ -358,6 +356,7 @@ private suspend fun doRemoveUser(m: ChatModel, user: User, users: List<User>, de m.controller.apiDeleteUser(user, delSMPQueues, viewPwd) m.controller.changeActiveUser_(user.remoteHostId, null, null) if (appPlatform.isAndroid) { + m.controller.apiStopChat() controller.appPrefs.onboardingStage.set(OnboardingStage.Step1_SimpleXInfo) ModalManager.closeAllModalsEverywhere() } diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml index 5903c67e9c..9fb161511d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -104,7 +104,7 @@ <string name="onboarding_notifications_mode_service_desc"><![CDATA[<b> تستهلك المزيد من البطارية </b>! تعمل خدمة الخلفية دائمًا - تظهر الإشعارات بمجرد توفر الرسائل.]]></string> <string name="call_already_ended">انتهت المكالمة بالفعل!</string> <string name="alert_title_msg_bad_hash">تجزئة رسالة سيئة</string> - <string name="integrity_msg_bad_id">معرف رسالة سيئ</string> + <string name="integrity_msg_bad_id">معرّف رسالة سيئ</string> <string name="icon_descr_call_ended">انتهت المكالمة</string> <string name="change_verb">تغير</string> <string name="color_primary_variant">لون إضافي ثانوي</string> @@ -132,7 +132,7 @@ <string name="icon_descr_audio_on">الصوت مفعل</string> <string name="auto_accept_images">قبول تلقائي للصور</string> <string name="integrity_msg_bad_hash">تجزئة رسالة سيئة</string> - <string name="alert_title_msg_bad_id">معرف رسالة سيئ</string> + <string name="alert_title_msg_bad_id">معرّف رسالة سيئ</string> <string name="app_passcode_replaced_with_self_destruct">يتم استبدال رمز مرور التطبيق برمز مرور التدمير الذاتي.</string> <string name="v4_6_audio_video_calls">مكالمات الصوت والفيديو</string> <string name="callstatus_error">خطأ في الاتصال</string> @@ -1628,4 +1628,19 @@ <string name="block_for_all_question">حظر العضو للجميع؟</string> <string name="blocked_by_admin_item_description">محظور من قبل المشرف</string> <string name="member_blocked_by_admin">محظور من قبل المشرف</string> + <string name="message_too_large">الرسالة كبيرة جدًا</string> + <string name="welcome_message_is_too_long">رسالة الترحيب طويلة جدًا</string> + <string name="database_migration_in_progress">ترحيل قاعدة البيانات قيد التقدم. +\nقد يستغرق بضع دقائق.</string> + <string name="call_service_notification_video_call">مكالمة فيديو</string> + <string name="call_service_notification_audio_call">مكالمة صوتية</string> + <string name="call_service_notification_end_call">أنهيّ المكالمة</string> + <string name="unable_to_open_browser_desc">متصفح الويب الافتراضي مطلوب للمكالمات. يُرجى تضبيط المتصفح الافتراضي في النظام، ومشاركة المزيد من المعلومات مع المطورين.</string> + <string name="unable_to_open_browser_title">حدث خطأ أثناء فتح المتصفح</string> + <string name="migrate_from_device_archive_and_upload">أرشفة و رفع</string> + <string name="v5_6_safer_groups_descr">يمكن للمشرفين حظر عضو للجميع.</string> + <string name="v5_6_app_data_migration">ترحيل بيانات التطبيق</string> + <string name="migrate_from_device_archiving_database">جارِ أرشفة قاعدة البيانات</string> + <string name="migrate_from_device_all_data_will_be_uploaded">سيتم تعمية جميع جهات الاتصال والمحادثات والملفات الخاصة بك بشكل آمن وتحميلها في أجزاء إلى مُرحلات XFTP التي ضبطت.</string> + <string name="migrate_to_device_apply_onion">طبّق</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 176ace6dd4..c2348568b1 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -54,6 +54,11 @@ <string name="decryption_error">Decryption error</string> <string name="encryption_renegotiation_error">Encryption re-negotiation error</string> + <string name="e2ee_info_no_pq"><![CDATA[Messages, files and calls are protected by <b>end-to-end encryption</b> with perfect forward secrecy, repudiation and break-in recovery.]]></string> + <string name="e2ee_info_pq"><![CDATA[Messages, files and calls are protected by <b>quantum resistant e2e encryption</b> with perfect forward secrecy, repudiation and break-in recovery.]]></string> + <string name="e2ee_info_no_pq_short">This chat is protected by end-to-end encryption.</string> + <string name="e2ee_info_pq_short">This chat is protected by quantum resistant end-to-end encryption.</string> + <!-- NoteFolder - ChatModel.kt --> <string name="note_folder_local_display_name">Private notes</string> @@ -177,6 +182,9 @@ <!-- SimpleX Chat foreground Service --> <string name="simplex_service_notification_title">SimpleX Chat service</string> <string name="simplex_service_notification_text">Receiving messages…</string> + <string name="call_service_notification_audio_call">Audio call</string> + <string name="call_service_notification_video_call">Video call</string> + <string name="call_service_notification_end_call">End call</string> <string name="hide_notification">Hide</string> <!-- Notification channels --> @@ -237,6 +245,7 @@ <string name="auth_stop_chat">Stop chat</string> <string name="auth_open_chat_console">Open chat console</string> <string name="auth_open_chat_profiles">Open chat profiles</string> + <string name="auth_open_migration_to_another_device">Open migration screen</string> <string name="lock_not_enabled">SimpleX Lock not enabled!</string> <string name="you_can_turn_on_lock">You can turn on SimpleX Lock via Settings.</string> @@ -801,6 +810,10 @@ <string name="callstate_connected">connected</string> <string name="callstate_ended">ended</string> + <!-- CallView --> + <string name="unable_to_open_browser_title">Error opening browser</string> + <string name="unable_to_open_browser_desc">The default web browser is required for calls. Please configure the default browser in the system, and share more information with the developers.</string> + <!-- SimpleXInfo --> <string name="next_generation_of_private_messaging">The next generation of private messaging</string> <string name="privacy_redefined">Privacy redefined</string> @@ -811,6 +824,7 @@ <string name="opensource_protocol_and_code_anybody_can_run_servers">Open-source protocol and code – anybody can run the servers.</string> <string name="create_your_profile">Create your profile</string> <string name="make_private_connection">Make a private connection</string> + <string name="migrate_from_another_device">Migrate from another device</string> <string name="how_it_works">How it works</string> <!-- How SimpleX Works --> @@ -1069,6 +1083,7 @@ <string name="confirm_new_passphrase">Confirm new passphrase…</string> <string name="update_database_passphrase">Update database passphrase</string> <string name="set_database_passphrase">Set database passphrase</string> + <string name="set_passphrase">Set passphrase</string> <string name="enter_correct_current_passphrase">Please enter correct current passphrase.</string> <string name="database_is_not_encrypted">Your chat database is not encrypted - set passphrase to protect it.</string> <string name="keychain_is_storing_securely">Android Keystore is used to securely store passphrase - it allows notification service to work.</string> @@ -1232,6 +1247,8 @@ <string name="snd_conn_event_ratchet_sync_started">agreeing encryption for %s…</string> <string name="snd_conn_event_ratchet_sync_agreed">encryption agreed for %s</string> <string name="rcv_conn_event_verification_code_reset">security code changed</string> + <string name="conn_event_enabled_pq">quantum resistant e2e encryption</string> + <string name="conn_event_disabled_pq">standard end-to-end encryption</string> <!-- GroupMemberRole --> <string name="group_member_role_observer">observer</string> @@ -1702,6 +1719,14 @@ <string name="v5_5_message_delivery">Improved message delivery</string> <string name="v5_5_message_delivery_descr">With reduced battery usage.</string> <string name="v5_5_new_interface_languages">Hungarian and Turkish UI</string> + <string name="v5_6_quantum_resistant_encryption">Quantum resistant encryption</string> + <string name="v5_6_quantum_resistant_encryption_descr">Enable in direct chats (BETA)!</string> + <string name="v5_6_app_data_migration">App data migration</string> + <string name="v5_6_app_data_migration_descr">Migrate to another device via QR code.</string> + <string name="v5_6_picture_in_picture_calls">Picture-in-picture calls</string> + <string name="v5_6_picture_in_picture_calls_descr">Use the app while in the call.</string> + <string name="v5_6_safer_groups">Safer groups</string> + <string name="v5_6_safer_groups_descr">Admins can block a member for all.</string> <!-- CustomTimePicker --> <string name="custom_time_unit_seconds">seconds</string> @@ -1828,4 +1853,67 @@ <string name="agent_internal_error_title">Internal error</string> <string name="agent_internal_error_desc">Please report it to the developers: \n%s</string> <string name="restart_chat_button">Restart chat</string> + + <!-- MigrateToDevice.kt --> + <string name="migrate_to_device_title">Migrate here</string> + <string name="or_paste_archive_link">Or paste archive link</string> + <string name="paste_archive_link">Paste archive link</string> + <string name="invalid_file_link">Invalid link</string> + <string name="migrate_to_device_migrating">Migrating</string> + <string name="migrate_to_device_database_init">Preparing download</string> + <string name="migrate_to_device_downloading_details">Downloading link details</string> + <string name="migrate_to_device_downloading_archive">Downloading archive</string> + <string name="migrate_to_device_bytes_downloaded">%s downloaded</string> + <string name="migrate_to_device_download_failed">Download failed</string> + <string name="migrate_to_device_repeat_download">Repeat download</string> + <string name="migrate_to_device_try_again">You can give another try.</string> + <string name="migrate_to_device_importing_archive">Importing archive</string> + <string name="migrate_to_device_import_failed">Import failed</string> + <string name="migrate_to_device_repeat_import">Repeat import</string> + <string name="migrate_to_device_enter_passphrase">Enter passphrase</string> + <string name="migrate_to_device_file_delete_or_link_invalid">File was deleted or link is invalid</string> + <string name="migrate_to_device_error_downloading_archive">Error downloading the archive</string> + <string name="migrate_to_device_chat_migrated">Chat migrated!</string> + <string name="migrate_to_device_finalize_migration">Finalize migration on another device.</string> + <string name="migrate_to_device_confirm_network_settings">Confirm network settings</string> + <string name="migrate_to_device_confirm_network_settings_footer">Please confirm that network settings are correct for this device.</string> + <string name="migrate_to_device_apply_onion">Apply</string> + + <!-- MigrateFromDevice.kt --> + <string name="migrate_from_device_title">Migrate device</string> + <string name="migrate_from_device_to_another_device">Migrate to another device</string> + <string name="migrate_from_device_error_saving_settings">Error saving settings</string> + <string name="migrate_from_device_exported_file_doesnt_exist">Exported file doesn\'t exist</string> + <string name="migrate_from_device_error_exporting_archive">Error exporting chat database</string> + <string name="migrate_from_device_database_init">Preparing upload</string> + <string name="migrate_from_device_error_uploading_archive">Error uploading the archive</string> + <string name="migrate_from_device_error_deleting_database">Error deleting database</string> + <string name="migrate_from_device_stopping_chat">Stopping chat</string> + <string name="migrate_from_device_chat_should_be_stopped">In order to continue, chat should be stopped.</string> + <string name="migrate_from_device_archive_and_upload">Archive and upload</string> + <string name="migrate_from_device_confirm_upload">Confirm upload</string> + <string name="migrate_from_device_all_data_will_be_uploaded">All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays.</string> + <string name="migrate_from_device_archiving_database">Archiving database</string> + <string name="migrate_from_device_bytes_uploaded">%s uploaded</string> + <string name="migrate_from_device_uploading_archive">Uploading archive</string> + <string name="migrate_from_device_upload_failed">Upload failed</string> + <string name="migrate_from_device_repeat_upload">Repeat upload</string> + <string name="migrate_from_device_try_again">You can give another try.</string> + <string name="migrate_from_device_creating_archive_link">Creating archive link</string> + <string name="migrate_from_device_cancel_migration">Cancel migration</string> + <string name="migrate_from_device_finalize_migration">Finalize migration</string> + <string name="migrate_from_device_choose_migrate_from_another_device"><![CDATA[Choose <i>Migrate from another device</i> on the new device and scan QR code.]]></string> + <string name="migrate_from_device_or_share_this_file_link">Or securely share this file link</string> + <string name="migrate_from_device_delete_database_from_device">Delete database from this device</string> + <string name="migrate_from_device_starting_chat_on_multiple_devices_unsupported">Warning: starting chat on multiple devices is not supported and will cause message delivery failures</string> + <string name="migrate_from_device_start_chat">Start chat</string> + <string name="migrate_from_device_migration_complete">Migration complete</string> + <string name="migrate_from_device_you_must_not_start_database_on_two_device"><![CDATA[You <b>must not</b> use the same database on two devices.]]></string> + <string name="migrate_from_device_using_on_two_device_breaks_encryption"><![CDATA[<b>Please note</b>: using the same database on two devices will break the decryption of messages from your connections, as a security protection.]]></string> + <string name="migrate_from_device_verify_database_passphrase">Verify database passphrase</string> + <string name="migrate_from_device_verify_passphrase">Verify passphrase</string> + <string name="migrate_from_device_confirm_you_remember_passphrase">Confirm that you remember database passphrase to migrate it.</string> + <string name="migrate_from_device_check_connection_and_try_again">Check your internet connection and try again</string> + <string name="migrate_from_device_archive_will_be_deleted"><![CDATA[<b>Warning</b>: the archive will be deleted.]]></string> + <string name="migrate_from_device_error_verifying_passphrase">Error verifying passphrase:</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml index e75634b536..bba2efcbf5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -181,7 +181,7 @@ <string name="change_database_passphrase_question">Промяна на паролата на базата данни\?</string> <string name="rcv_group_event_changed_member_role">променена ролята от %s на %s</string> <string name="invite_prohibited">Не може да покани контакта!</string> - <string name="rcv_conn_event_switch_queue_phase_completed">променен е адреса за вас</string> + <string name="rcv_conn_event_switch_queue_phase_completed">адреса за изпращане е променен</string> <string name="change_verb">Промени</string> <string name="change_member_role_question">Промяна на груповата роля\?</string> <string name="you_will_still_receive_calls_and_ntfs">Все още ще получавате обаждания и известия от заглушени профили, когато са активни.</string> @@ -834,7 +834,7 @@ <string name="v4_3_voice_messages_desc">Макс. 40 секунди, получават се незабавно.</string> <string name="v4_4_live_messages">Съобщения на живо</string> <string name="live_message">Съобщение на живо!</string> - <string name="verify_security_code">Потвръди кода за сигурност</string> + <string name="verify_security_code">Потвърди кода за сигурност</string> <string name="no_details">няма подробности</string> <string name="ok">ОК</string> <string name="ask_your_contact_to_enable_voice">Моля, попитайте вашия контакт, за да активирате изпращане на гласови съобщения.</string> @@ -866,7 +866,7 @@ <string name="feature_offered_item_with_param">предлага %s: %2s</string> <string name="whats_new_read_more">Прочетете още</string> <string name="v5_2_disappear_one_message">Накарайте едно съобщение да изчезне</string> - <string name="v4_4_verify_connection_security">Потвръди сигурността на връзката</string> + <string name="v4_4_verify_connection_security">Потвърди сигурността на връзката</string> <string name="v4_5_message_draft">Чернова на съобщение</string> <string name="v5_2_more_things_descr">- по-стабилна доставка на съобщения. \n- малко по-добри групи. @@ -1246,7 +1246,7 @@ <string name="voice_messages_prohibited">Гласовите съобщения са забранени!</string> <string name="you_need_to_allow_to_send_voice">Трябва да разрешите на вашия контакт да изпраща гласови съобщения, за да можете да ги изпращате.</string> <string name="you_are_invited_to_group">Поканени сте в групата</string> - <string name="snd_conn_event_switch_queue_phase_completed">променихте адреса</string> + <string name="snd_conn_event_switch_queue_phase_completed">адреса за получаване е променен</string> <string name="you_can_share_this_address_with_your_contacts">Можете да споделите този адрес с вашите контакти, за да им позволите да се свържат с %s.</string> <string name="unfavorite_chat">Премахни от любимите</string> <string name="settings_section_title_you">ВИЕ</string> @@ -1288,7 +1288,7 @@ <string name="incognito_random_profile">Вашият автоматично генериран профил</string> <string name="user_unmute">Уведомявай</string> <string name="you_can_share_your_address">Можете да споделите адреса си като линк или QR код - всеки може да се свърже с вас.</string> - <string name="snd_conn_event_switch_queue_phase_completed_for_member">променихте адреса за %s</string> + <string name="snd_conn_event_switch_queue_phase_completed_for_member">променихте адреса получаване за %s</string> <string name="group_main_profile_sent">Вашият чат профил ще бъде изпратен на членовете на групата</string> <string name="your_chat_profile_will_be_sent_to_your_contact">Вашият чат профил ще бъде изпратен \nдо вашия контакт</string> @@ -1432,7 +1432,7 @@ <string name="rcv_group_event_1_member_connected">%s свързан</string> <string name="random_port">Случаен</string> <string name="linked_desktops">Запомнени настолни устройства</string> - <string name="discover_on_network">Открий през локалната мрежа</string> + <string name="discover_on_network">Откриване през локалната мрежа</string> <string name="rcv_group_and_other_events">и %d други събития</string> <string name="connect_plan_connect_via_link">Свърване чрез линк?</string> <string name="v5_4_incognito_groups">Инкогнито групи</string> @@ -1485,7 +1485,7 @@ <string name="unblock_member_desc">Съобщенията от %s ще бъдат показани!</string> <string name="this_device_name_shared_with_mobile">Името на устройството ще бъде споделено със свързания мобилен клиент.</string> <string name="error_sending_message_contact_invitation">Грешка при изпращане на покана</string> - <string name="verify_code_on_mobile">Потвръди кода на мобилното устройство</string> + <string name="verify_code_on_mobile">Потвърди кода на мобилното устройство</string> <string name="open_port_in_firewall_title">Отвори порт в защитната стена</string> <string name="enter_this_device_name">Въведи името на това устройство…</string> <string name="error">Грешка</string> @@ -1501,7 +1501,7 @@ <string name="bad_desktop_address">Грешен адрес на настолното устройство</string> <string name="paste_desktop_address">Постави адрес на настолно устройство</string> <string name="connect_plan_this_is_your_link_for_group_vName"><![CDATA[Това е вашят линк за група <b>%1$s</b>!]]></string> - <string name="verify_code_with_desktop">Потвръди кода с настолното устройство</string> + <string name="verify_code_with_desktop">Потвърди кода с настолното устройство</string> <string name="scan_qr_code_from_desktop">Сканирай QR код от настолното устройство</string> <string name="unblock_member_confirmation">Отблокирай</string> <string name="devices">Устройства</string> @@ -1513,12 +1513,12 @@ <string name="connect_plan_you_have_already_requested_connection_via_this_address">Вече сте заявили връзка през този адрес!</string> <string name="terminal_always_visible">Покажи конзолата в нов прозорец</string> <string name="scan_from_mobile">Сканиране от мобилно устройство</string> - <string name="verify_connections">Потвръди връзките</string> + <string name="verify_connections">Потвърждение за свързване</string> <string name="disconnect_desktop_question">Прекъсни връзката с настолното устройство?</string> <string name="loading_remote_file_desc">Моля, изчакайте, докато файлът се зареди от свързаното мобилно устройство</string> <string name="block_member_desc">Всички нови съобщения от %s ще бъдат скрити!</string> <string name="desktop_app_version_is_incompatible">Версията на настолното приложение %s не е съвместима с това приложение.</string> - <string name="verify_connection">Потвръди връзките</string> + <string name="verify_connection">Потвърди връзка</string> <string name="no_connected_mobile">Няма свързано мобилно устройство</string> <string name="blocked_item_description">блокиран</string> <string name="you_can_make_address_visible_via_settings">Можете да го направите видим за вашите контакти в SimpleX чрез Настройки.</string> @@ -1590,4 +1590,50 @@ \nПрепоръчително е да рестартирате приложението.</string> <string name="developer_options_section">Опции за разработчици</string> <string name="show_slow_api_calls">Показване на бавни API заявки</string> + <string name="v5_5_private_notes_descr">С криптирани файлове и медия.</string> + <string name="v5_5_simpler_connect_ui">Поставете линк, за да се свържете!</string> + <string name="v5_5_private_notes">Лични бележки</string> + <string name="v5_5_message_delivery">Подобрена доставка на съобщения</string> + <string name="v5_5_message_delivery_descr">С намален разход на батерията.</string> + <string name="blocked_by_admin_item_description">блокиран от админ</string> + <string name="blocked_by_admin_items_description">%d съобщения, блокирани от администратора</string> + <string name="error_creating_message">Грешка при създаване на съобщение</string> + <string name="error_deleting_note_folder">Грешка при изтриване на лични бележки</string> + <string name="clear_note_folder_question">Изчистване на лични бележки?</string> + <string name="clear_note_folder_warning">Всички съобщения ще бъдат изтрити - това не може да бъде отменено!</string> + <string name="rcv_group_event_member_blocked">блокиран %s</string> + <string name="rcv_group_event_member_unblocked">отблокиран %s</string> + <string name="snd_group_event_member_blocked">вие блокирахте %s</string> + <string name="snd_group_event_member_unblocked">вие отблокирахте %s</string> + <string name="info_row_created_at">Създаден на</string> + <string name="share_text_created_at">Създаден на: %s</string> + <string name="saved_message_title">Запазено съобщение</string> + <string name="block_for_all_question">Блокиране на член за всички?</string> + <string name="block_for_all">Блокирай за всички</string> + <string name="unblock_for_all_question">Отблокиране на член за всички?</string> + <string name="unblock_for_all">Отблокирай за всички</string> + <string name="member_blocked_by_admin">Блокиран от админ</string> + <string name="member_info_member_blocked">блокиран</string> + <string name="error_blocking_member_for_all">Грешка при блокиране на член за всички</string> + <string name="v5_5_simpler_connect_ui_descr">Лентата за търсене приема линк за връзка.</string> + <string name="v5_5_join_group_conversation">Присъединяване към групи</string> + <string name="v5_5_new_interface_languages">Унгарски и турски потребителски интерфейс</string> + <string name="v5_5_join_group_conversation_descr">Скорошна история и подобрен бот за директорията за групи.</string> + <string name="profile_update_event_member_name_changed">името на члена %1$s е променено на %2$s</string> + <string name="profile_update_event_contact_name_changed">името на контакта %1$s е променено на %2$s</string> + <string name="profile_update_event_removed_picture">премахната профилна снимка</string> + <string name="profile_update_event_set_new_picture">зададена нова профилна снимка</string> + <string name="profile_update_event_removed_address">премахнат адрес за контакт</string> + <string name="profile_update_event_set_new_address">зададен нов адрес за контакт</string> + <string name="profile_update_event_updated_profile">актуализиран профил</string> + <string name="note_folder_local_display_name">Лични бележки</string> + <string name="message_too_large">Съобщението е твърде голямо</string> + <string name="welcome_message_is_too_long">Съобщението при посрещане е твърде голямо</string> + <string name="database_migration_in_progress">Извършва се миграция на базата данни. +\nМоже да отнеме няколко минути.</string> + <string name="call_service_notification_audio_call">Аудио разговор</string> + <string name="unable_to_open_browser_title">Грешка при отваряне на браузъра</string> + <string name="unable_to_open_browser_desc">За разговори е необходим уеб браузър по подразбиране. Моля, конфигурирайте браузъра по подразбиране в системните настройки и споделете повече информация с разработчиците.</string> + <string name="call_service_notification_video_call">Видео разговор</string> + <string name="call_service_notification_end_call">Край на разговора</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index 1134129845..364fde803d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -70,7 +70,7 @@ <string name="feature_received_prohibited">přijaté, zakázané</string> <string name="both_you_and_your_contact_can_send_disappearing">Vy i váš kontakt můžete posílat mizící zprávy.</string> <string name="only_your_contact_can_send_disappearing">Zmizelé zprávy může odesílat pouze váš kontakt.</string> - <string name="only_you_can_delete_messages">Nevratně mazat zprávy můžete pouze vy (váš kontakt je může označit ke smazání).</string> + <string name="only_you_can_delete_messages">Nevratně mazat zprávy můžete pouze vy (váš kontakt je může označit ke smazání). (24 hodin)</string> <string name="message_deletion_prohibited">Nevratné mazání zpráv je v tomto chatu zakázáno.</string> <string name="prohibit_direct_messages">Přímé zprávy členům zakázány.</string> <string name="ttl_sec">%d sec</string> @@ -85,8 +85,8 @@ <string name="v4_4_verify_connection_security_desc">Porovnejte bezpečnostní kódy se svými kontakty.</string> <string name="app_name">SimpleX</string> <string name="thousand_abbreviation">k</string> - <string name="connect_via_contact_link">Připojit přez adresu kontaktu?</string> - <string name="connect_via_invitation_link">Připojit přez jednorázovou pozvánku?</string> + <string name="connect_via_contact_link">Připojit přes kontaktní adresu?</string> + <string name="connect_via_invitation_link">Připojit přes jednorázovou pozvánku?</string> <string name="connect_via_group_link">Připojit se do skupiny?</string> <string name="profile_will_be_sent_to_contact_sending_link">Váš profil bude odeslán kontaktu, od kterého jste obdrželi tento odkaz.</string> <string name="server_connected">připojeno</string> @@ -473,7 +473,7 @@ <string name="incognito_info_share">Pokud s někým sdílíte inkognito profil, bude použit pro skupiny, do kterých vás pozve.</string> <string name="theme_system">Systémové</string> <string name="voice_messages">Hlasové zprávy</string> - <string name="both_you_and_your_contacts_can_delete">Vy i váš kontakt můžete nevratně mazat odeslané zprávy.</string> + <string name="both_you_and_your_contacts_can_delete">Vy i váš kontakt můžete nevratně mazat odeslané zprávy. (24 hodin)</string> <string name="ttl_m">%dm</string> <string name="ttl_mth">%dmth</string> <string name="ttl_hours">%d hodin</string> @@ -687,7 +687,7 @@ <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="paste_the_link_you_received">Vložení přijatého odkazu</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> <string name="contact_wants_to_connect_via_call">%1$s se s vámi chce spojit prostřednictvím</string> @@ -902,7 +902,7 @@ <string name="prohibit_sending_voice_messages">Hlasové zprávy zakázány.</string> <string name="only_you_can_send_disappearing">Pouze vy můžete odesílat mizící zprávy.</string> <string name="disappearing_prohibited_in_this_chat">Mizící zprávy jsou v tomto chatu zakázány.</string> - <string name="only_your_contact_can_delete">Nevratně mazat zprávy může pouze váš kontakt (vy je můžete označit ke smazání).</string> + <string name="only_your_contact_can_delete">Nevratně mazat zprávy může pouze váš kontakt (vy je můžete označit ke smazání). (24 hodin)</string> <string name="both_you_and_your_contact_can_send_voice">Hlasové zprávy můžete posílat vy i váš kontakt.</string> <string name="only_you_can_send_voice">Hlasové zprávy můžete posílat pouze vy.</string> <string name="only_your_contact_can_send_voice">Hlasové zprávy může odesílat pouze váš kontakt.</string> @@ -914,7 +914,7 @@ <string name="disappearing_messages_are_prohibited">Mizící zprávy jsou v této skupině zakázány.</string> <string name="group_members_can_send_dms">Členové skupiny mohou posílat přímé zprávy.</string> <string name="direct_messages_are_prohibited_in_chat">Přímé zprávy mezi členy jsou v této skupině zakázány.</string> - <string name="group_members_can_delete">Členové skupiny mohou nevratně mazat odeslané zprávy.</string> + <string name="group_members_can_delete">Členové skupin mohou nevratně mazat odeslané zprávy. (24 hodin)</string> <string name="message_deletion_prohibited_in_chat">Nevratné mazání zpráv je v této skupině zakázáno.</string> <string name="group_members_can_send_voice">Členové skupiny mohou posílat hlasové zprávy.</string> <string name="voice_messages_are_prohibited">Hlasové zprávy jsou v této skupině zakázány.</string> @@ -1066,7 +1066,7 @@ <string name="enable_lock">Povolit zámek</string> <string name="lock_after">Zamknout po</string> <string name="lock_mode">Režim zámku</string> - <string name="authentication_cancelled">Autentizace zrušena</string> + <string name="authentication_cancelled">Ověření zrušeno</string> <string name="confirm_passcode">Potvrdit heslo</string> <string name="incorrect_passcode">Nesprávné heslo</string> <string name="new_passcode">Nové heslo</string> @@ -1436,15 +1436,15 @@ <string name="v5_4_block_group_members">Blokovat členy skupin</string> <string name="connect_plan_repeat_connection_request">Opakovat žádost o připojení?</string> <string name="encryption_renegotiation_error">Chyba opětovného vyjednávání šifrování</string> - <string name="connect_plan_you_are_already_connecting_to_vName">Již se připojujete k %1$s.</string> + <string name="connect_plan_you_are_already_connecting_to_vName"><![CDATA[Již se připojujete k <b>%1$s</b>.]]></string> <string name="v5_4_incognito_groups_descr">Vytvořit skupinu pomocí náhodného profilu.</string> <string name="connect_plan_you_are_already_joining_the_group_via_this_link">Již se připojujete ke skupině prostřednictvím tohoto odkazu.</string> <string name="refresh_qr_code">Obnovit</string> <string name="new_mobile_device">Nový telefon</string> <string name="only_one_device_can_work_at_the_same_time">Pouze jedno zařízení může pracovat současně</string> <string name="you_can_make_address_visible_via_settings">Můžete ji svým SimpleX kontaktům zviditelnit v Nastavení.</string> - <string name="connect_plan_you_are_already_joining_the_group_vName">Již se připojujete ke skupině %1$s.</string> - <string name="v5_4_link_mobile_desktop">Připojit mobilní a stolní aplikace! 🔗</string> + <string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[Již se připojujete ke skupině <b>%1$s</b>.]]></string> + <string name="v5_4_link_mobile_desktop">Propojit mobilní a stolní aplikace! 🔗</string> <string name="connect_plan_this_is_your_own_one_time_link">To je váš vlastní jednorázový odkaz!</string> <string name="marked_deleted_items_description">%d zpráv označeno jako smazaná</string> <string name="v5_4_link_mobile_desktop_descr">Prostřednictvím zabezpečeného kvant rezistentního protokolu.</string> @@ -1459,9 +1459,9 @@ \nProsím vyzkoušejte jiné video, nebo kontaktujte vývojáře.</string> <string name="random_port">Náhodný</string> <string name="linked_desktops">Připojená PC</string> - <string name="discover_on_network">Objevit přes lokální síť</string> + <string name="discover_on_network">Objevte přes lokální síť</string> <string name="rcv_group_and_other_events">a %d jiných událostí</string> - <string name="connect_plan_connect_via_link">Připojit přez odkaz?</string> + <string name="connect_plan_connect_via_link">Připojit přes odkaz?</string> <string name="v5_4_incognito_groups">Inkognito skupiny</string> <string name="connect_plan_already_joining_the_group">Už se ke skupině připojujete!</string> <string name="group_members_n">%s, %s a %d členů</string> @@ -1475,7 +1475,7 @@ <string name="linked_mobiles">Připojené telefony</string> <string name="connected_to_desktop">Připojeno k PC</string> <string name="contact_tap_to_connect">Klepněte na Připojit se</string> - <string name="connect_plan_you_are_already_in_group_vName">Již jste ve skupině %1$s.</string> + <string name="connect_plan_you_are_already_in_group_vName"><![CDATA[Již jste ve skupině <b>%1$s</b>.]]></string> <string name="connect_plan_this_is_your_own_simplex_address">To je vaše vlastní SimpleX adresa!</string> <string name="loading_remote_file_title">Načítám soubor</string> <string name="connecting_to_desktop">Připojuji k PC</string> @@ -1510,11 +1510,11 @@ <string name="disconnect_remote_hosts">Odpojit mobilní telefony</string> <string name="block_member_question">Blokovat člena?</string> <string name="rcv_group_events_count">%d skupinových událostí</string> - <string name="connect_plan_this_is_your_link_for_group_vName">To je váš odkaz pro skupinu %1$s!</string> + <string name="connect_plan_this_is_your_link_for_group_vName"><![CDATA[To je váš odkaz pro skupinu <b>%1$s</b>!]]></string> <string name="verify_code_with_desktop">Ověřit kód s PC</string> <string name="scan_qr_code_from_desktop">Skenovat QR kód z PC</string> <string name="unblock_member_confirmation">Odblokovat</string> - <string name="multicast_discoverable_via_local_network">Objevitelný prostřednictvím lokální sítě</string> + <string name="multicast_discoverable_via_local_network">Objevitelný přes lokální síť</string> <string name="v5_4_more_things_descr">- volitelně oznámení odstraněným kontaktům. \n- profilová jména s mezeramy. \n- a více!</string> @@ -1522,7 +1522,7 @@ <string name="connect_plan_you_have_already_requested_connection_via_this_address">Již jste požádali o spojení přes tuto adresu!</string> <string name="terminal_always_visible">Zobrazit konzoli v novém okně</string> <string name="scan_from_mobile">Skenování z telefonu</string> - <string name="verify_connections">Ověřit více připojení</string> + <string name="verify_connections">Ověřit připojení</string> <string name="loading_remote_file_desc">Prosím počkejte, než bude soubor načten z propojeného telefonu</string> <string name="block_member_desc">Všechny nové zprávy od %s budou skryté!</string> <string name="desktop_app_version_is_incompatible">Verze aplikace na PC %s není kompatibilní s touto aplikací.</string> @@ -1557,4 +1557,156 @@ <string name="recent_history_is_not_sent_to_new_members">Historie není odeslána novým členům.</string> <string name="recent_history_is_sent_to_new_members">Až 100 poslední zprávy je odesláno novým členům.</string> <string name="chat_is_stopped_you_should_transfer_database">Chat je zastaven. Pokud jste již použili tuto databázi na jiném zařízení, měli byste ji před zahájením chatu převést zpět.</string> + <string name="possible_slow_function_desc">Provedení funkce trvá příliš dlouho: %1$d vteřin: %2$s</string> + <string name="v5_5_join_group_conversation">Připojit se ke skupině</string> + <string name="blocked_by_admin_item_description">blokováno adminem</string> + <string name="blocked_by_admin_items_description">%d zpráv zablokováno správcem</string> + <string name="error_creating_message">Chyba vytváření zprávy</string> + <string name="error_deleting_note_folder">Chyba odstranění soukromých poznámek</string> + <string name="clear_note_folder_question">Smazat soukromé poznámky?</string> + <string name="clear_note_folder_warning">Všechny zprávy budou smazány - nemůže být zvráceno!</string> + <string name="developer_options_section">Možnosti vývojáře</string> + <string name="rcv_group_event_member_blocked">blokováno %s</string> + <string name="profile_update_event_contact_name_changed">kontakt %1$s změnen na %2$s</string> + <string name="info_row_created_at">Vytvořeno v</string> + <string name="block_for_all">Blok všem</string> + <string name="block_for_all_question">Blokovat člena všem?</string> + <string name="error_blocking_member_for_all">Chyba blokování člena všem</string> + <string name="v5_5_message_delivery">Vylepšené doručovaní zpráv</string> + <string name="v5_5_new_interface_languages">Maďarské a Turecké uživatelské rozhraní</string> + <string name="remote_host_error_bad_state"><![CDATA[Připojení k mobilu <b>%s</b> je ve špatném stavu]]></string> + <string name="remote_ctrl_error_inactive">PC je neaktivní</string> + <string name="remote_ctrl_error_bad_state">Připojení k PC je ve špatném stavu</string> + <string name="remote_ctrl_error_busy">PC je zaneprázdněno</string> + <string name="remote_ctrl_error_disconnected">PC byl odpojeno</string> + <string name="agent_critical_error_title">Kritická chyba</string> + <string name="remote_ctrl_error_bad_version">PC má nepodporovanou verzi. Ujistěte se, že používáte stejnou verzi na obou zařízeních</string> + <string name="remote_ctrl_error_bad_invitation">PC má chybný kód pozvánky</string> + <string name="failed_to_create_user_invalid_title">Neplatné jméno!</string> + <string name="database_migration_in_progress">Databáze migrace běží. +\nMůže to trvat několik minut.</string> + <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="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> + <string name="remote_ctrl_was_disconnected_title">Spojení zastaveno</string> + <string name="remote_ctrl_disconnected_with_reason">Odpojeno z důvodu: %s</string> + <string name="agent_internal_error_title">Interní chyba</string> + <string name="v5_5_simpler_connect_ui_descr">Vyhledávání přijímá pozvánky.</string> + <string name="show_slow_api_calls">Zobrazit pomalé API volání</string> + <string name="profile_update_event_removed_address">odstraněna kontaktní adresa</string> + <string name="past_member_vName">Vložte člena %1$s</string> + <string name="v5_5_simpler_connect_ui">Vložte odkaz na připojení!</string> + <string name="v5_5_join_group_conversation_descr">Nejnovější historie a vylepšený directory bot.</string> + <string name="v5_5_private_notes">Soukromé poznámky</string> + <string name="remote_host_error_inactive"><![CDATA[Mobilní <b>%s</b> je neaktivní]]></string> + <string name="remote_host_error_busy"><![CDATA[Mobilní <b>%s</b> je zaneprázdněn]]></string> + <string name="remote_host_error_bad_version"><![CDATA[Mobile <b>%s</b> má nepodporovanou verzi. Ujistěte se, že používáte stejnou verzi na obou zařízeních]]></string> + <string name="agent_internal_error_desc">Nahlaste to prosím vývojářům: +\n%s</string> + <string name="show_internal_errors">Zobrazit interní chyby</string> + <string name="agent_critical_error_desc">Nahlaste to prosím vývojářům: +\n%s +\n +\nDoporučuje se restartovat aplikaci.</string> + <string name="restart_chat_button">Restartovat chat</string> + <string name="remote_host_error_disconnected"><![CDATA[Mobilní <b>%s</b> byl odpojen]]></string> + <string name="unable_to_open_browser_title">Chyba otevření prohlížeče</string> + <string name="profile_update_event_removed_picture">odstraněn profilový obrázek</string> + <string name="profile_update_event_set_new_address">nastavit novou kontaktní adresu</string> + <string name="profile_update_event_set_new_picture">nastavit nový profilový obrázek</string> + <string name="saved_message_title">Uložené zprávy</string> + <string name="possible_slow_function_title">Pomalá funkce</string> + <string name="note_folder_local_display_name">Soukromé poznámky</string> + <string name="call_service_notification_audio_call">Volání</string> + <string name="call_service_notification_end_call">Konec volání</string> + <string name="remote_host_error_missing"><![CDATA[Mobilní <b>%s</b> chybí]]></string> + <string name="snd_group_event_member_blocked">zablokovali jste %s</string> + <string name="snd_group_event_member_unblocked">odblokovaly jste %s</string> + <string name="group_member_status_unknown_short">neznámý</string> + <string name="v5_5_message_delivery_descr">Se sníženou spotřebou baterie.</string> + <string name="group_member_status_unknown">neznámý stav</string> + <string name="unblock_for_all_question">Odblokovat člena všem?</string> + <string name="unblock_for_all">Odblokovat pro všechny</string> + <string name="welcome_message_is_too_long">Uvítací zpráva je příliš dlouhá</string> + <string name="remote_host_error_timeout"><![CDATA[Vypršel čas při připojování k mobilnímu <b>%s</b>]]></string> + <string name="call_service_notification_video_call">Videohovor</string> + <string name="profile_update_event_updated_profile">profil aktualizován</string> + <string name="unable_to_open_browser_desc">Výchozí webový prohlížeč je vyžadován pro volání. Nakonfigurujte výchozí prohlížeč v systému a sdílejte s vývojáři více informací.</string> + <string name="rcv_group_event_member_unblocked">odblokované %s</string> + <string name="v5_5_private_notes_descr">S šifrovanými soubory a médii.</string> + <string name="remote_ctrl_error_timeout">Vypršel čas při připojování k desktopu</string> + <string name="failed_to_create_user_invalid_desc">Zobrazené jméno není platné. Prosím vyberte jiné.</string> + <string name="migrate_to_device_chat_migrated">Chat přesunut!</string> + <string name="migrate_to_device_apply_onion">Použít</string> + <string name="migrate_from_device_choose_migrate_from_another_device"><![CDATA[Zvolte <i>Přesunot z jiného zařízení</i> na novém zařízení a skenujte QR kód.]]></string> + <string name="migrate_to_device_confirm_network_settings">Potvrdit nastavení sítě</string> + <string name="migrate_from_device_confirm_upload">Potvrďte nahrání</string> + <string name="migrate_from_device_archiving_database">Archivuji databázi</string> + <string name="migrate_from_device_cancel_migration">Zrušit migraci</string> + <string name="migrate_from_device_check_connection_and_try_again">Zkontrolujte připojení k internetu a zkuste to znovu</string> + <string name="migrate_from_device_confirm_you_remember_passphrase">Pro migraci potvrďte, že si pamatujete heslo databáze.</string> + <string name="migrate_from_device_archive_will_be_deleted"><![CDATA[<b>Varování</b>: archiv bude smazán.]]></string> + <string name="migrate_from_device_archive_and_upload">Archivovat a nahrát</string> + <string name="v5_6_safer_groups_descr">Správci mohou blokovat člen pro všechny.</string> + <string name="migrate_from_device_all_data_will_be_uploaded">Všechny vaše kontakty, konverzace a soubory budou bezpečně šifrovány a rozdělené nahrány na zvolené XFTP relé.</string> + <string name="v5_6_app_data_migration">Migrace dat aplikace</string> + <string name="migrate_from_device_using_on_two_device_breaks_encryption"><![CDATA[<b>Čtěte prosím</b>: Jako ochrana zabezpečení, se použitím stejné databáze na dvou zařízeních rozbije dešifrování zpráv z vašich připojení.]]></string> + <string name="auth_open_migration_to_another_device">Otevřít obrazovku přesunu</string> + <string name="set_passphrase">Nastavit přístupovou frázi</string> + <string name="migrate_to_device_migrating">Přesouvám</string> + <string name="migrate_to_device_confirm_network_settings_footer">Prosím potvrďte, že nastavení sítě pro toto zařízení jsou správná.</string> + <string name="migrate_from_device_error_exporting_archive">Chyba při exportu chat databáze</string> + <string name="migrate_from_device_database_init">Příprava nahrávání</string> + <string name="migrate_from_device_repeat_upload">Opakovat nahrávání</string> + <string name="migrate_from_device_upload_failed">Nahrání neúspěšné</string> + <string name="migrate_from_device_try_again">Můžete zkusit znovu.</string> + <string name="migrate_from_device_starting_chat_on_multiple_devices_unsupported">Upozornění: zahájení chatu na více zařízeních není podporováno a způsobí selhání doručování zpráv</string> + <string name="migrate_from_device_migration_complete">Přesun kompletní</string> + <string name="v5_6_app_data_migration_descr">Migrovat na jiné zařízení pomocí QR kódu.</string> + <string name="v5_6_picture_in_picture_calls">Volání obraz v obraze</string> + <string name="v5_6_safer_groups">Bezpečnější skupiny</string> + <string name="v5_6_picture_in_picture_calls_descr">Používat aplikaci při volání.</string> + <string name="migrate_to_device_downloading_archive">Stáhnuji archiv</string> + <string name="migrate_to_device_downloading_details">Stáhuji podrobnosti o odkazu</string> + <string name="invalid_file_link">Neplatný odkaz</string> + <string name="or_paste_archive_link">Nebo vložte odkaz archivu</string> + <string name="paste_archive_link">Vložit odkaz archivu</string> + <string name="migrate_to_device_database_init">Příprava stahování</string> + <string name="migrate_to_device_bytes_downloaded">%s staženo</string> + <string name="migrate_to_device_import_failed">Import selhal</string> + <string name="migrate_to_device_importing_archive">Importuji archiv</string> + <string name="migrate_to_device_try_again">Můžete zkusit znovu.</string> + <string name="migrate_to_device_error_downloading_archive">Chyba stahování archivu</string> + <string name="migrate_from_device_error_saving_settings">Chyba ukládání nastavení</string> + <string name="migrate_from_device_error_uploading_archive">Chyba nahrávání archivu</string> + <string name="migrate_from_device_exported_file_doesnt_exist">Exportovaný soubor neexistuje</string> + <string name="migrate_to_device_file_delete_or_link_invalid">Soubor byl odstraněn nebo je odkaz neplatný</string> + <string name="migrate_to_device_finalize_migration">Dokončit migraci na jiném zařízení.</string> + <string name="migrate_from_device_title">Migrovat zařízení</string> + <string name="migrate_from_device_to_another_device">Migrovat na jiné zařízení</string> + <string name="migrate_from_device_error_deleting_database">Chyba mazání databáze</string> + <string name="migrate_from_device_chat_should_be_stopped">Chcete-li pokračovat, zastavte chat.</string> + <string name="migrate_from_device_stopping_chat">Zastavuji chat</string> + <string name="migrate_from_device_creating_archive_link">Vytvořit odkaz archivu</string> + <string name="migrate_from_device_bytes_uploaded">%s nahráno</string> + <string name="migrate_from_device_uploading_archive">Nahrávám archiv</string> + <string name="migrate_from_device_finalize_migration">Dokončit migraci</string> + <string name="migrate_from_device_or_share_this_file_link">Nebo bezpečně sdílejte tento odkaz na soubor</string> + <string name="migrate_from_device_start_chat">Začít chat</string> + <string name="migrate_from_device_verify_database_passphrase">Ověřit přístupovou frázi databáze</string> + <string name="migrate_from_device_you_must_not_start_database_on_two_device"><![CDATA[<b>nesmíte</b> použít stejnou databázi na dvou zařízeních.]]></string> + <string name="migrate_from_device_verify_passphrase">Ověřit přístupovou frázi</string> + <string name="migrate_from_device_error_verifying_passphrase">Chyba ověření přístupové fráze:</string> + <string name="migrate_from_device_delete_database_from_device">Odstranit databázi z tohoto zařízení</string> + <string name="migrate_to_device_download_failed">Stažení selhalo</string> + <string name="v5_6_quantum_resistant_encryption_descr">Povolit v přímém chatu (BETA)!</string> + <string name="migrate_to_device_enter_passphrase">Zadejte přístupovou frázi</string> + <string name="migrate_from_another_device">Migrovat z jiného zařízení</string> + <string name="migrate_to_device_title">Migrovat zde</string> + <string name="migrate_to_device_repeat_download">Opakovat stahování</string> + <string name="migrate_to_device_repeat_import">Opakovat import</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml index 5ec604c259..3102d56120 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -50,7 +50,7 @@ <!-- SimpleXAPI.kt --> <string name="error_saving_smp_servers">Fehler beim Speichern der SMP-Server</string> <string name="ensure_smp_server_address_are_correct_format_and_unique">Stellen Sie sicher, dass die SMP-Server-Adressen das richtige Format haben, zeilenweise getrennt und nicht doppelt vorhanden sind.</string> - <string name="error_setting_network_config">Fehler bei der Aktualisierung der Netzwerk-Konfiguration.</string> + <string name="error_setting_network_config">Fehler bei der Aktualisierung der Netzwerkkonfiguration.</string> <!-- API Error Responses - SimpleXAPI.kt --> <string name="connection_timeout">Verbindungszeitüberschreitung</string> <string name="connection_error">Verbindungsfehler</string> @@ -68,7 +68,7 @@ <string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Überprüfen Sie bitte, ob Sie den richtigen Link genutzt haben, oder bitten Sie Ihren Kontakt darum, Ihnen nochmal einen Link zuzusenden.</string> <string name="connection_error_auth">Verbindungsfehler (AUTH)</string> <string name="connection_error_auth_desc">Entweder hat Ihr Kontakt die Verbindung gelöscht, oder dieser Link wurde bereits verwendet, es könnte sich um einen Fehler handeln – bitte melden Sie ihn uns. -\nBitten Sie Ihren Kontakt darum, einen weiteren Verbindungs-Link zu erzeugen, um sich neu verbinden zu können, und stellen Sie sicher, dass Sie eine stabile Netzwerk-Verbindung haben.</string> +\nBitten Sie Ihren Kontakt darum, einen weiteren Verbindungs-Link zu erzeugen, um sich neu verbinden zu können, und stellen Sie sicher, dass Sie eine stabile Netzwerkverbindung haben.</string> <string name="error_accepting_contact_request">Fehler beim Akzeptieren der Kontaktanfrage</string> <string name="sender_may_have_deleted_the_connection_request">Der Absender hat möglicherweise die Verbindungsanfrage gelöscht.</string> <string name="error_deleting_contact">Fehler beim Löschen des Kontakts</string> @@ -1687,7 +1687,7 @@ <string name="v5_5_simpler_connect_ui">Zum Verbinden den Link einfügen!</string> <string name="v5_5_message_delivery_descr">Mit reduziertem Akkuverbrauch.</string> <string name="v5_5_join_group_conversation_descr">Aktueller Nachrichtenverlauf und verbesserter Gruppenverzeichnis-Bot.</string> - <string name="v5_5_simpler_connect_ui_descr">Von der Suchleiste werden Einladungslinks akzeptiert.</string> + <string name="v5_5_simpler_connect_ui_descr">In der Suchleiste werden nun auch Einladungslinks akzeptiert.</string> <string name="unblock_for_all">Für Alle freigeben</string> <string name="unblock_for_all_question">Mitglied für Alle freigeben?</string> <string name="member_info_member_blocked">wurde blockiert</string> @@ -1703,12 +1703,97 @@ <string name="v5_5_message_delivery">Verbesserte Zustellung von Nachrichten</string> <string name="v5_5_join_group_conversation">Gruppenunterhaltungen beitreten</string> <string name="v5_5_new_interface_languages">Ungarische und türkische Bedienoberfläche</string> - <string name="profile_update_event_contact_name_changed">Kontaktname %1$s wurde auf %2$s geändert</string> - <string name="profile_update_event_member_name_changed">Mitgliedsname %1$s wurde auf %2$s geändert</string> - <string name="profile_update_event_set_new_address">Neue Kontaktadresse wurde festgelegt</string> - <string name="profile_update_event_set_new_picture">Neues Profil-Bild wurde festgelegt</string> + <string name="profile_update_event_contact_name_changed">Der Kontaktname wurde von %1$s auf %2$s geändert</string> + <string name="profile_update_event_member_name_changed">Der Mitgliedsname von %1$s wurde auf %2$s geändert</string> + <string name="profile_update_event_set_new_address">Es wurde eine neue Kontaktadresse festgelegt</string> + <string name="profile_update_event_set_new_picture">Es wurde ein neues Profil-Bild festgelegt</string> <string name="profile_update_event_updated_profile">Das Profil wurde aktualisiert</string> - <string name="profile_update_event_removed_picture">Profil-Bild wurde entfernt</string> - <string name="profile_update_event_removed_address">Kontaktadresse wurde entfernt</string> + <string name="profile_update_event_removed_picture">Das Profil-Bild wurde entfernt</string> + <string name="profile_update_event_removed_address">Die Kontaktadresse wurde entfernt</string> <string name="note_folder_local_display_name">Private Notizen</string> + <string name="database_migration_in_progress">Momentan wird eine Datenbank-Migration durchgeführt. +\nDies kann einige Minuten andauern.</string> + <string name="message_too_large">Die Nachricht ist zu lang</string> + <string name="welcome_message_is_too_long">Die Begrüßungsmeldung ist zu lang</string> + <string name="call_service_notification_audio_call">Audioanruf</string> + <string name="call_service_notification_end_call">Anruf beenden</string> + <string name="call_service_notification_video_call">Videoanruf</string> + <string name="unable_to_open_browser_title">Fehler beim Öffnen des Browsers</string> + <string name="unable_to_open_browser_desc">Für Anrufe ist ein Default-Webbrowser erforderlich. Bitte konfigurieren Sie einen Default-Browser für das System und teilen Sie den Entwicklern mehr Informationen dazu mit.</string> + <string name="migrate_to_device_chat_migrated">Chat wurde migriert!</string> + <string name="migrate_from_device_creating_archive_link">Archiv-Link erzeugen</string> + <string name="migrate_from_device_choose_migrate_from_another_device"><![CDATA[Wählen Sie auf dem neuen Gerät <i>Von einem anderen Gerät migrieren</i> und scannen Sie den QR-Code.]]></string> + <string name="migrate_from_device_delete_database_from_device">Datenbank auf diesem Gerät löschen</string> + <string name="v5_6_app_data_migration">App-Daten-Migration</string> + <string name="v5_6_safer_groups_descr">Administratoren können für ein Mitglied alle Funktionen blockieren.</string> + <string name="migrate_to_device_downloading_details">Link-Details werden heruntergeladen</string> + <string name="migrate_to_device_downloading_archive">Archiv wird heruntergeladen</string> + <string name="migrate_to_device_apply_onion">Anwenden</string> + <string name="migrate_from_device_all_data_will_be_uploaded">Alle Ihre Kontakte, Unterhaltungen und Dateien werden sicher verschlüsselt und in Daten-Paketen auf die konfigurierten XTFP-Server hochgeladen.</string> + <string name="migrate_from_device_archive_and_upload">Archivieren und Hochladen</string> + <string name="migrate_from_device_archive_will_be_deleted"><![CDATA[<b>Warnung</b>: Das Archiv wird gelöscht.]]></string> + <string name="migrate_from_device_check_connection_and_try_again">Überprüfen Sie Ihre Internet-Verbindung und probieren Sie es nochmals</string> + <string name="migrate_from_device_archiving_database">Datenbank wird archiviert</string> + <string name="migrate_from_device_using_on_two_device_breaks_encryption"><![CDATA[<b>Bitte beachten Sie</b>: Aus Sicherheitsgründen wird die Nachrichtenentschlüsselung Ihrer Verbindungen abgebrochen, wenn Sie die gleiche Datenbank auf zwei Geräten nutzen.]]></string> + <string name="migrate_from_device_cancel_migration">Migration abbrechen</string> + <string name="migrate_to_device_confirm_network_settings">Bestätigen Sie die Netzwerkeinstellungen</string> + <string name="migrate_from_device_confirm_you_remember_passphrase">Für die Migration bestätigen Sie bitte, dass Sie sich an das Datenbank-Passwort erinnern.</string> + <string name="migrate_from_device_confirm_upload">Hochladen bestätigen</string> + <string name="migrate_to_device_download_failed">Herunterladen fehlgeschlagen</string> + <string name="e2ee_info_no_pq"><![CDATA[Nachrichten, Dateien und Anrufe sind durch <b>Ende-zu-Ende-Verschlüsselung</b> mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt.]]></string> + <string name="e2ee_info_pq"><![CDATA[Nachrichten, Dateien und Anrufe sind durch <b>Quantum-resistente E2E-Verschlüsselung</b> mit Perfect Forward Secrecy, Ablehnung und Einbruchs-Wiederherstellung geschützt.]]></string> + <string name="e2ee_info_no_pq_short">Dieser Chat ist durch Ende-zu-Ende-Verschlüsselung geschützt.</string> + <string name="e2ee_info_pq_short">Dieser Chat ist durch Quantum-resistente Ende-zu-Ende-Verschlüsselung geschützt.</string> + <string name="auth_open_migration_to_another_device">Migrationsansicht öffnen</string> + <string name="set_passphrase">Passwort festlegen</string> + <string name="conn_event_enabled_pq">Quantum-resistente E2E-Verschlüsselung</string> + <string name="or_paste_archive_link">Oder fügen Sie den Archiv-Link ein</string> + <string name="paste_archive_link">Archiv-Link einfügen</string> + <string name="invalid_file_link">Ungültiger Link</string> + <string name="migrate_to_device_migrating">Migrieren</string> + <string name="migrate_to_device_bytes_downloaded">%s heruntergeladen</string> + <string name="migrate_to_device_finalize_migration">Die Migration auf dem anderen Gerät wird abgeschlossen.</string> + <string name="migrate_to_device_error_downloading_archive">Fehler beim Herunterladen des Archivs</string> + <string name="migrate_from_device_finalize_migration">Die Migration wird abgeschlossen</string> + <string name="migrate_from_device_starting_chat_on_multiple_devices_unsupported">Warnung: Das Starten des Chats auf mehreren Geräten wird nicht unterstützt und wird zu Fehlern bei der Nachrichtenübermittlung führen</string> + <string name="migrate_from_device_start_chat">Chat starten</string> + <string name="v5_6_quantum_resistant_encryption_descr">Kann in direkten Chats aktiviert werden (BETA)!</string> + <string name="v5_6_quantum_resistant_encryption">Quantum-resistente Verschlüsselung</string> + <string name="v5_6_app_data_migration_descr">Über einen QR-Code auf ein anderes Gerät migrieren.</string> + <string name="v5_6_picture_in_picture_calls">Bild-in-Bild-Anrufe</string> + <string name="v5_6_safer_groups">Sicherere Gruppen</string> + <string name="v5_6_picture_in_picture_calls_descr">Die App kann während eines Anrufs genutzt werden.</string> + <string name="migrate_to_device_title">Hierher migrieren</string> + <string name="migrate_to_device_database_init">Das Herunterladen wird vorbereitet</string> + <string name="migrate_to_device_repeat_download">Herunterladen wiederholen</string> + <string name="migrate_to_device_try_again">Sie können es nochmal probieren.</string> + <string name="migrate_to_device_enter_passphrase">Passwort eingeben</string> + <string name="migrate_to_device_importing_archive">Archiv wird importiert</string> + <string name="migrate_to_device_repeat_import">Import wiederholen</string> + <string name="migrate_to_device_file_delete_or_link_invalid">Die Datei wurde gelöscht oder der Link ist ungültig</string> + <string name="migrate_from_device_title">Gerät migrieren</string> + <string name="migrate_from_device_to_another_device">Auf ein anderes Gerät migrieren</string> + <string name="migrate_from_device_error_exporting_archive">Fehler beim Exportieren der Chat-Datenbank</string> + <string name="migrate_from_device_error_saving_settings">Fehler beim Abspeichern der Einstellungen</string> + <string name="migrate_from_device_exported_file_doesnt_exist">Die exportierte Datei ist nicht vorhanden</string> + <string name="migrate_from_device_error_deleting_database">Fehler beim Löschen der Datenbank</string> + <string name="migrate_from_device_error_uploading_archive">Fehler beim Hochladen des Archivs</string> + <string name="migrate_from_device_database_init">Das Hochladen wird vorbereitet</string> + <string name="migrate_from_device_chat_should_be_stopped">Um fortzufahren, sollte der Chat beendet werden.</string> + <string name="migrate_from_device_bytes_uploaded">%s hochgeladen</string> + <string name="migrate_from_device_uploading_archive">Archiv wird hochgeladen</string> + <string name="migrate_from_device_stopping_chat">Chat wird beendet</string> + <string name="migrate_from_device_or_share_this_file_link">Oder teilen Sie diesen Datei-Link sicher</string> + <string name="migrate_from_device_repeat_upload">Hochladen wiederholen</string> + <string name="migrate_from_device_upload_failed">Hochladen fehlgeschlagen</string> + <string name="migrate_from_device_verify_database_passphrase">Überprüfen Sie das Datenbank-Passwort</string> + <string name="migrate_from_device_try_again">Sie können es nochmal probieren.</string> + <string name="migrate_from_device_error_verifying_passphrase">Fehler bei der Überprüfung des Passworts:</string> + <string name="migrate_from_device_verify_passphrase">Überprüfen Sie das Passwort</string> + <string name="migrate_from_another_device">Von einem anderen Gerät migrieren</string> + <string name="migrate_to_device_import_failed">Import ist fehlgeschlagen</string> + <string name="migrate_from_device_migration_complete">Migration abgeschlossen</string> + <string name="migrate_to_device_confirm_network_settings_footer">Bitte bestätigen Sie, dass die Netzwerkeinstellungen auf diesem Gerät richtig sind.</string> + <string name="conn_event_disabled_pq">Standard-Ende-zu-Ende-Verschlüsselung</string> + <string name="migrate_from_device_you_must_not_start_database_on_two_device"><![CDATA[Sie dürfen die selbe Datenbank <b>nicht</b> auf zwei Geräten nutzen.]]></string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml index f67a753bb5..14fe05cd2e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -87,7 +87,7 @@ <string name="database_will_be_encrypted_and_passphrase_stored">La base de datos será cifrada y la contraseña se guardará en Keystore.</string> <string name="delete_contact_question">¿Eliminar contacto\?</string> <string name="delete_message__question">¿Eliminar mensaje\?</string> - <string name="delete_chat_profile_question">¿Eliminar el perfil de chat\?</string> + <string name="delete_chat_profile_question">¿Eliminar perfil de chat?</string> <string name="rcv_group_event_group_deleted">grupo eliminado</string> <string name="delete_group_question">¿Eliminar grupo\?</string> <string name="delete_messages_after">Eliminar en</string> @@ -173,7 +173,7 @@ <string name="group_member_status_connecting">conectando</string> <string name="group_member_status_creator">creador</string> <string name="ttl_min">%d min</string> - <string name="connection_timeout">Tiempo de conexión expirado</string> + <string name="connection_timeout">Tiempo de conexión agotado</string> <string name="failed_to_create_user_duplicate_title">¡Nombre mostrado duplicado!</string> <string name="connection_error">Error conexión</string> <string name="smp_server_test_create_queue">Crear cola</string> @@ -194,7 +194,7 @@ <string name="delete_chat_archive_question">¿Eliminar archivo del chat\?</string> <string name="create_group_link">Crear enlace de grupo</string> <string name="delete_link">Eliminar enlace</string> - <string name="users_delete_question">¿Eliminar el perfil de chat\?</string> + <string name="users_delete_question">¿Eliminar perfil de chat?</string> <string name="ttl_hour">%d hora</string> <string name="ttl_day">%d día</string> <string name="v4_5_multiple_chat_profiles_descr">Nombre y avatar diferentes, aislamiento de transporte.</string> @@ -437,7 +437,7 @@ <string name="delete_message_cannot_be_undone_warning">El mensaje será eliminado. ¡No podrá deshacerse!</string> <string name="incognito_info_protects">El modo incógnito protege tu privacidad creando un perfil aleatorio por cada contacto.</string> <string name="turn_off_battery_optimization"><![CDATA[Para usar SimpleX, por favor <b>permite que SimpleX se ejecute en segundo plano</b> en el siguiente cuadro de diálogo. De lo contrario las notificaciones se desactivarán.]]></string> - <string name="install_simplex_chat_for_terminal">Instalar terminal para SimpleX Chat</string> + <string name="install_simplex_chat_for_terminal">Instalar terminal de SimpleX Chat</string> <string name="group_invitation_item_description">invitación al grupo %1$s</string> <string name="rcv_group_event_member_added">ha invitado a %1$s</string> <string name="incognito_info_allows">Permite tener varias conexiones anónimas sin datos compartidos entre estas dentro del mismo perfil.</string> @@ -476,7 +476,7 @@ <string name="settings_notifications_mode_title">Servicio</string> <string name="delete_message_mark_deleted_warning">El mensaje se marcará para eliminar. El destinatario o destinatarios podrán revelar este mensaje.</string> <string name="live_message">¡Mensaje en vivo!</string> - <string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app"><![CDATA[📱 móvil: pulse <b>Abrir en aplicación móvil</b>, después pulse <b>Conectar</b> en la aplicación.]]></string> + <string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app"><![CDATA[📱 móvil: pulsa <b>Abrir en aplicación móvil</b>, después pulsa <b>Conectar</b> en la aplicación.]]></string> <string name="mark_read">Marcar como leído</string> <string name="mark_unread">Marcar como no leído</string> <string name="invalid_QR_code">Código QR no válido</string> @@ -565,7 +565,7 @@ <string name="rcv_group_event_member_left">ha salido</string> <string name="button_leave_group">Salir del grupo</string> <string name="only_group_owners_can_change_prefs">Sólo los propietarios pueden modificar las preferencias del grupo.</string> - <string name="users_delete_data_only">Sólo datos del perfil local</string> + <string name="users_delete_data_only">Sólo datos del perfil</string> <string name="chat_preferences_no">no</string> <string name="thousand_abbreviation">k</string> <string name="marked_deleted_description">marcado eliminado</string> @@ -723,7 +723,7 @@ <string name="settings_section_title_socks">PROXY SOCKS</string> <string name="settings_section_title_themes">TEMAS</string> <string name="stop_chat_confirmation">Detener</string> - <string name="delete_chat_profile_action_cannot_be_undone_warning">Esta acción no se puede deshacer. Tu perfil, contactos, mensajes y archivos se perderán irreversiblemente.</string> + <string name="delete_chat_profile_action_cannot_be_undone_warning">Esta acción es irreversible. Tu perfil, contactos, mensajes y archivos se perderán.</string> <string name="skip_inviting_button">Omitir invitación a miembros</string> <string name="settings_notification_preview_mode_title">Vista previa</string> <string name="la_notice_turn_on">Activar</string> @@ -751,8 +751,8 @@ <string name="database_initialization_error_desc">La base de datos no funciona correctamente. Pulsa para saber más</string> <string name="moderate_message_will_be_marked_warning">El mensaje será marcado como moderado para todos los miembros.</string> <string name="next_generation_of_private_messaging">La nueva generación de mensajería privada</string> - <string name="delete_files_and_media_desc">Esta acción no se puede deshacer. Se eliminarán todos los archivos y multimedia recibidos y enviados. Las imágenes de baja resolución permanecerán.</string> - <string name="enable_automatic_deletion_message">Esta acción no se puede deshacer. Se eliminarán los mensajes enviados y recibidos anteriores a la selección. Puede tardar varios minutos.</string> + <string name="delete_files_and_media_desc">Esta acción es irreversible. Se eliminarán todos los archivos y multimedia recibidos y enviados. Las imágenes de baja resolución permanecerán.</string> + <string name="enable_automatic_deletion_message">Esta acción es irreversible. Los mensajes enviados y recibidos anteriores a la selección serán eliminados. Podría tardar varios minutos.</string> <string name="messages_section_description">Esta configuración se aplica a los mensajes del perfil actual</string> <string name="this_string_is_not_a_connection_link">¡Esta cadena no es un enlace de conexión!</string> <string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery"><![CDATA[Para preservar tu privacidad, en lugar de notificaciones automáticas la aplicación cuenta con un <b>servicio en segundo planoSimpleX</b>, usa un pequeño porcentaje de la batería al día.]]></string> @@ -822,9 +822,9 @@ <string name="member_role_will_be_changed_with_invitation">El rol del miembro cambiará a \"%s\" y recibirá una invitación nueva.</string> <string name="update_network_settings_confirmation">Actualizar</string> <string name="update_network_settings_question">¿Actualizar la configuración de red\?</string> - <string name="trying_to_connect_to_server_to_receive_messages">Intentando conectar con el servidor usado para recibir mensajes de este contacto.</string> + <string name="trying_to_connect_to_server_to_receive_messages">Intentando conectar con el servidor para recibir mensajes de este contacto.</string> <string name="unknown_message_format">formato de mensaje desconocido</string> - <string name="trying_to_connect_to_server_to_receive_messages_with_error">Intentando conectar con el servidor usado para recibir mensajes de este contacto (error: %1$s ).</string> + <string name="trying_to_connect_to_server_to_receive_messages_with_error">Intentando conectar con el servidor para recibir mensajes de este contacto (error: %1$s).</string> <string name="error_smp_test_failed_at_step">Prueba fallida en el paso %s.</string> <string name="tap_to_start_new_chat">Pulsa para iniciar chat nuevo</string> <string name="share_message">Compartir mensaje…</string> @@ -880,7 +880,7 @@ <string name="alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry">Tu contacto debe estar en línea para que se complete la conexión. \nPuedes cancelar esta conexión y eliminar el contacto (e intentarlo más tarde con un enlace nuevo).</string> <string name="your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one">La base de datos actual será ELIMINADA y SUSTITUIDA por la importada. -\nEsta acción no podrá deshacerse. Tu perfil, contactos, mensajes y archivos actuales se perderán irreversiblemente.</string> +\nEsta acción es irreversible. Tu perfil, contactos, mensajes y archivos actuales se perderán.</string> <string name="incognito_random_profile">Tu perfil aleatorio</string> <string name="you_will_be_connected_when_your_connection_request_is_accepted">Te conectarás cuando tu solicitud se acepte, por favor espera o compruébalo más tarde.</string> <string name="you_will_be_connected_when_your_contacts_device_is_online">Te conectarás cuando el dispositivo de tu contacto esté en línea, por favor espera o compruébalo más tarde.</string> @@ -1081,12 +1081,12 @@ <string name="la_mode_system">Sistema</string> <string name="decryption_error">Error descifrado</string> <string name="alert_text_msg_bad_id">El ID del siguiente mensaje es incorrecto (menor o igual que el anterior). -\nPuede ocurrir por algún bug o cuando la conexión está comprometida.</string> +\nPuede ocurrir por algún error o si la conexión está comprometida.</string> <string name="alert_text_fragment_please_report_to_developers">Por favor, informa a los desarrolladores.</string> <string name="alert_text_decryption_error_too_many_skipped">%1$d mensaje(s) omitido(s).</string> <string name="alert_title_msg_bad_hash">Hash de mensaje incorrecto</string> <string name="alert_title_msg_bad_id">ID de mensaje incorrecto</string> - <string name="alert_text_fragment_encryption_out_of_sync_old_database">Puede ocurrir cuando tu o tu contacto estáis usando una copia de seguridad antigua de la base de datos.</string> + <string name="alert_text_fragment_encryption_out_of_sync_old_database">Puede ocurrir si tu contacto o tu usáis una copia de seguridad antigua de la base de datos.</string> <string name="alert_text_msg_bad_hash">El hash del mensaje anterior es diferente.</string> <string name="alert_text_decryption_error_n_messages_failed_to_decrypt">%1$d mensaje(s) no ha(n) podido ser descifrado(s).</string> <string name="stop_file__action">Detener archivo</string> @@ -1521,12 +1521,12 @@ <string name="terminal_always_visible">Mostrar consola en ventana nueva</string> <string name="scan_from_mobile">Escáner desde móvil</string> <string name="verify_connections">Verificar conexiones</string> - <string name="loading_remote_file_desc">Por favor, espere mientras el archivo se carga desde el móvil enlazado</string> + <string name="loading_remote_file_desc">Por favor, espera mientras el archivo se carga desde el móvil enlazado</string> <string name="verify_connection">Verificar conexión</string> <string name="no_connected_mobile">Ningún móvil conectado</string> <string name="app_was_crashed">Error aplicación</string> <string name="error_showing_content">error mostrando contenido</string> - <string name="error_showing_message">error mostrando mensaje</string> + <string name="error_showing_message">error al mostrar mensaje</string> <string name="you_can_make_address_visible_via_settings">Puedes hacerlo visible para tus contactos de SimpleX en Configuración.</string> <string name="recent_history_is_not_sent_to_new_members">El historial no se envía a miembros nuevos.</string> <string name="retry_verb">Reintentar</string> @@ -1555,4 +1555,97 @@ <string name="keep_invitation_link">Guardar</string> <string name="tap_to_paste_link">Pulsa para pegar enlace</string> <string name="search_or_paste_simplex_link">Buscar o pegar enlace SimpleX</string> + <string name="v5_5_message_delivery_descr">Con uso reducido de batería.</string> + <string name="blocked_by_admin_item_description">bloqueado por el administrador</string> + <string name="blocked_by_admin_items_description">%d mensajes bloqueados por el administrador</string> + <string name="error_creating_message">Error al crear mensaje</string> + <string name="error_deleting_note_folder">Error al borrar notas privadas</string> + <string name="clear_note_folder_question">¿Borrar notas privadas?</string> + <string name="developer_options_section">Opciones desarrollador</string> + <string name="rcv_group_event_member_blocked">%s bloqueado</string> + <string name="rcv_group_event_member_unblocked">%s desbloqueado</string> + <string name="snd_group_event_member_blocked">has bloqueado a %s</string> + <string name="snd_group_event_member_unblocked">has desbloqueado a %s</string> + <string name="group_member_status_unknown_short">desconocido</string> + <string name="info_row_created_at">Creado</string> + <string name="block_for_all_question">¿Bloqear miembro para todos?</string> + <string name="share_text_created_at">Creado: %s</string> + <string name="block_for_all">Bloquear para todos</string> + <string name="unblock_for_all_question">¿Desbloquear miembro para todos?</string> + <string name="unblock_for_all">Desbloquear para todos</string> + <string name="member_info_member_blocked">bloqueado</string> + <string name="member_blocked_by_admin">Bloqueado por el administrador</string> + <string name="error_blocking_member_for_all">Error al bloqear el miembro para todos</string> + <string name="v5_5_private_notes_descr">Con cifrado de archivos y multimedia.</string> + <string name="remote_host_error_timeout"><![CDATA[Tiempo de espera para conectar con el móvil <b>%s</b> agotado]]></string> + <string name="chat_is_stopped_you_should_transfer_database">Chat detenido. Si has usado esta base de datos en otro dispositivo, deberías transferirla de vuelta antes de iniciar el chat.</string> + <string name="failed_to_create_user_invalid_desc">Éste nombre mostrado no es válido. Por favor, elije otro nombre.</string> + <string name="remote_host_was_disconnected_title">Conexión detenida</string> + <string name="remote_ctrl_was_disconnected_title">Conexión detenida</string> + <string name="remote_host_disconnected_from"><![CDATA[Desconectado del móvil <b>%s</b> debido a: %s]]></string> + <string name="remote_ctrl_disconnected_with_reason">Desconectado debido a: %s</string> + <string name="remote_host_error_bad_state"><![CDATA[La conexión con el móvil <b>%s</b> es deficiente]]></string> + <string name="remote_ctrl_error_bad_state">La conexión al ordenador es deficiente</string> + <string name="remote_ctrl_error_inactive">El ordenador está inactivo</string> + <string name="remote_ctrl_error_busy">El ordenador está ocupado</string> + <string name="agent_critical_error_title">Error crítico</string> + <string name="clear_note_folder_warning">Todos los mensajes serán borrados. ¡No podrá deshacerse!</string> + <string name="start_chat_question">¿Iniciar chat?</string> + <string name="welcome_message_is_too_long">Mensaje de bienvenida demasiado largo</string> + <string name="remote_ctrl_error_timeout">Tiempo de espera para conectar con el ordenador agotado</string> + <string name="remote_ctrl_error_bad_invitation">El ordenador tiene un código de invitación incorrecto</string> + <string name="remote_ctrl_error_disconnected">El ordenador ha sido desconectado</string> + <string name="group_member_status_unknown">estado desconocido</string> + <string name="database_migration_in_progress">Migración de la base de datos en progreso. +\nPuede tardar varios minutos.</string> + <string name="remote_ctrl_error_bad_version">El ordenador tiene una versión sin soporte. Por favor, asegúrate de usar la misma versión en ambos dispositivos</string> + <string name="profile_update_event_contact_name_changed">el contacto %1$s ha cambiado a %2$s</string> + <string name="profile_update_event_updated_profile">perfil actualizado</string> + <string name="v5_5_private_notes">Notas privadas</string> + <string name="v5_5_message_delivery">Entrega de mensajes mejorada</string> + <string name="remote_host_error_bad_version"><![CDATA[El móvil <b>%s</b> tiene una versión sin soporte. Por favor, asegúrate de usar la misma versión en ambos dispositivos]]></string> + <string name="note_folder_local_display_name">Notas privadas</string> + <string name="possible_slow_function_desc">La ejecución de la función está tardando mucho: %1$d segundos: %2$s</string> + <string name="possible_slow_function_title">Función lenta</string> + <string name="show_slow_api_calls">Mostrar llamadas lentas de API</string> + <string name="saved_message_title">Mensaje guardado</string> + <string name="v5_5_simpler_connect_ui">Pegar enlace para conectar!</string> + <string name="v5_5_simpler_connect_ui_descr">La barra de búsqueda acepta enlaces de invitación.</string> + <string name="v5_5_join_group_conversation">Unirse a la conversación del grupo</string> + <string name="v5_5_join_group_conversation_descr">Historial reciente y bot del directorio mejorado.</string> + <string name="v5_5_new_interface_languages">Interfaz en Turco y en Húngaro</string> + <string name="remote_host_error_inactive"><![CDATA[El móvil <b>%s</b> está inactivo]]></string> + <string name="remote_host_error_missing"><![CDATA[El móvil <b>%s</b> no se encuentra]]></string> + <string name="remote_host_error_busy"><![CDATA[El móvil <b>%s</b> está ocupado]]></string> + <string name="agent_internal_error_title">Error interno</string> + <string name="show_internal_errors">Mostrar errores internos</string> + <string name="failed_to_create_user_invalid_title">¡Nombre mostrado no válido!</string> + <string name="agent_critical_error_desc">Por favor, informa a los desarrolladores: +\n%s +\n +\nSe recomienda reiniciar la aplicación.</string> + <string name="message_too_large">Mensaje demasiado largo</string> + <string name="remote_host_error_disconnected"><![CDATA[El móvil <b>%s</b> se ha desconectado]]></string> + <string name="agent_internal_error_desc">Por favor, informa a los desarrolladores: +\n%s</string> + <string name="restart_chat_button">Reiniciar chat</string> + <string name="past_member_vName">Miembro pasado %1$s</string> + <string name="profile_update_event_member_name_changed">el miembro %1$s ha cambiado a %2$s</string> + <string name="profile_update_event_removed_address">dirección de contacto eliminada</string> + <string name="profile_update_event_removed_picture">imagen de perfil eliminada</string> + <string name="profile_update_event_set_new_address">nueva dirección de contacto</string> + <string name="profile_update_event_set_new_picture">nueva imagen de perfil</string> + <string name="call_service_notification_audio_call">Llamada</string> + <string name="call_service_notification_end_call">Llamada finalizada</string> + <string name="call_service_notification_video_call">Videollamada</string> + <string name="unable_to_open_browser_title">Error al abrir el navegador</string> + <string name="unable_to_open_browser_desc">Para llamadas se requiere el navegador web predeterminado. Por favor, configura el navegador predeterminado en el sistema y comparte más información con los desarrolladores.</string> + <string name="migrate_from_device_archiving_database">Archivando base de datos</string> + <string name="migrate_from_device_all_data_will_be_uploaded">Todos tus contactos, conversaciones y archivos serán cifrados, divididos y subidos de forma segura a los servidores XFTP configurados.</string> + <string name="v5_6_safer_groups_descr">Los administradores pueden bloquear un miembro para el resto</string> + <string name="migrate_to_device_chat_migrated">Chat migrado!</string> + <string name="v5_6_app_data_migration">Migración de los datos de la app</string> + <string name="migrate_to_device_apply_onion">Aplicar</string> + <string name="migrate_from_device_archive_and_upload">Archivar y subir</string> + <string name="migrate_from_device_cancel_migration">Cancelar migración</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml index 47ea9f2e4b..53d9859802 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml @@ -113,4 +113,47 @@ <string name="failed_to_create_user_duplicate_desc">شما یک نمایه گپ با نام نمایشی یکسان دارید، لطفا نام دیگری انتخاب کنید.</string> <string name="error_creating_address">خطا در ایجاد نشانی</string> <string name="ensure_xftp_server_address_are_correct_format_and_unique">مطمئن شوید قالب آدرس‌های سرور XFTP صحیح است، در خط‌های جدا نوشته شده و تکرار نشده‌اند.</string> + <string name="error_accepting_contact_request">خطا در پذیرش درخواست مخاطب</string> + <string name="sender_may_have_deleted_the_connection_request">فرستنده ممکن است درخواست اتصال را حذف کرده باشد.</string> + <string name="error_deleting_note_folder">خطا در حذف یادداشت‌های خصوصی</string> + <string name="error_aborting_address_change">خطا در لغو تغییر نشانی</string> + <string name="error_synchronizing_connection">خطا در انطباق زمانی اتصال</string> + <string name="error_smp_test_failed_at_step">آزمایش در گام %s ناموفق بود.</string> + <string name="error_xftp_test_server_auth">سرور برای بارگذازی به اجازه نیاز دارد، گذرواژه را بررسی کنید</string> + <string name="error_smp_test_certificate">احتمال دارد اثر انگشت گواهینامه در نشانی سرور نادرست باشد</string> + <string name="smp_server_test_create_queue">ایجاد صف</string> + <string name="smp_server_test_upload_file">بارگذاری پرونده</string> + <string name="smp_server_test_download_file">بارگیری پرونده</string> + <string name="smp_server_test_compare_file">مقایسه پرونده</string> + <string name="smp_server_test_delete_file">حذف پرونده</string> + <string name="error_deleting_user">خطا در حذف نمایه کاربر</string> + <string name="error_updating_user_privacy">خطا در به‌روزرسانی حریم خصوصی کاربر</string> + <string name="error_deleting_contact">خطا در حذف مخاطب</string> + <string name="error_deleting_group">خطا در حذف گروه</string> + <string name="error_deleting_contact_request">خطا در حذف درخواست مخاطب</string> + <string name="error_deleting_pending_contact_connection">خطا در حذف اتصال معلق مخاطب</string> + <string name="error_changing_address">خطا در تغییر نشانی</string> + <string name="error_setting_address">خطا در تنظیم نشانی</string> + <string name="error_alert_title">خطا</string> + <string name="smp_server_test_connect">اتصال</string> + <string name="smp_server_test_disconnect">قطع اتصال</string> + <string name="smp_server_test_secure_queue">ایمن‌سازی صف</string> + <string name="smp_server_test_delete_queue">حذف صف</string> + <string name="smp_server_test_create_file">ایجاد پرونده</string> + <string name="error_smp_test_server_auth">سرور برای ایجاد صف داده‌ها به اجازه نیاز دارد، گذرواژه را بررسی کنید</string> + <string name="connection_error_auth_desc">مگر اینکه مخاطبتان اتصال را حذف کرده یا این لینک قبلا استفاده شده باشد، ممکن است این یک اشکال باشد - لطفا آن را گزارش دهید. +\nبرای متصل شدن، لطفا از مخاطبتان بخواهید لینک اتصال دیگری ایجاد کند و بررسی کنید که اتصال شبکه باثباتی دارید.</string> + <string name="possible_slow_function_title">عملکرد کند</string> + <string name="possible_slow_function_desc">اجرای این عملکرد زمان زیادی می‌گیرد: %1$d ثانیه: %2$s</string> + <string name="icon_descr_instant_notifications">اعلان‌های آنی</string> + <string name="service_notifications_disabled">اعلان‌های آنی غیرفعال شده‌اند!</string> + <string name="service_notifications">اعلان‌های آنی!</string> + <string name="note_folder_local_display_name">یادداشت‌های خصوصی</string> + <string name="blocked_by_admin_item_description">مسدود شده توسط مدیر</string> + <string name="blocked_by_admin_items_description">%d پیام توسط مدیر مسدود شده</string> + <string name="it_can_disabled_via_settings_notifications_still_shown"><![CDATA[<b>به وسیله تنظیمات می‌تواند غیرفعال شود</b> – اعلان‌ها تا زمانی که برنامه در حال اجراست، همچنان نمایش داده می‌شوند.]]></string> + <string name="database_migration_in_progress">جابه‌جایی پایگاه داده در حال جریان است. +\nممکن است دقایقی زمان ببرد.</string> + <string name="turn_off_battery_optimization"><![CDATA[برای استفاده از آن، لطفا در دیالوگ بعدی<b>به SimpleX اجازه دهید در پس‌زمینه اجرا شود</b>. در غیر این صورت، اعلان‌ها غیرفعال خواهند شد.]]></string> + <string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery"><![CDATA[برای حفاظت از حریم خصوصیتان، به جای اعلان‌های رانشی، برنامه یک <b>سرویس پس‌زمنیه SimpleX</b> دارد – سرویس، هر روز درصد معدودی از باتری را استفاده می‌کند.]]></string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml index b43eb64082..ead3957aa5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -89,7 +89,7 @@ <string name="periodic_notifications_desc">L\'application récupère périodiquement les nouveaux messages - elle utilise un peu votre batterie chaque jour. L\'application n\'utilise pas les notifications push - les données de votre appareil ne sont pas envoyées aux serveurs.</string> <string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery"><![CDATA[Pour protéger votre vie privée, au lieu des notifications push, l\'application possède un <b>SimpleX service de fond</b> - il utilise quelques pour cent de la batterie par jour.]]></string> <string name="hide_notification">Cacher</string> - <string name="settings_notification_preview_mode_title">Montrer l\'aperçu</string> + <string name="settings_notification_preview_mode_title">Afficher l\'aperçu</string> <string name="notification_preview_mode_contact">Nom du contact</string> <string name="notification_preview_somebody">Contact masqué :</string> <string name="notification_preview_new_message">nouveau message</string> @@ -410,7 +410,7 @@ <string name="smp_servers_your_server_address">Votre adresse de serveur</string> <string name="smp_servers_invalid_address">Adresse de serveur invalide !</string> <string name="smp_servers_check_address">Vérifiez l\'adresse du serveur et réessayez.</string> - <string name="using_simplex_chat_servers">Utilise les serveurs SimpleX Chat.</string> + <string name="using_simplex_chat_servers">Vous utilisez les serveurs SimpleX.</string> <string name="how_to">Comment faire</string> <string name="enter_one_ICE_server_per_line">Serveurs ICE (un par ligne)</string> <string name="error_saving_ICE_servers">Erreur lors de la sauvegarde des serveurs ICE</string> @@ -584,7 +584,7 @@ <string name="your_calls">Vos appels</string> <string name="always_use_relay">Se connecter via relais</string> <string name="call_on_lock_screen">Appels en écran verrouillé :</string> - <string name="show_call_on_lock_screen">Montrer</string> + <string name="show_call_on_lock_screen">Afficher</string> <string name="no_call_on_lock_screen">Désactiver</string> <string name="your_ice_servers">Vos serveurs ICE</string> <string name="webrtc_ice_servers">Serveurs WebRTC ICE</string> @@ -684,7 +684,7 @@ <string name="snd_group_event_changed_role_for_yourself">vous avez modifié votre rôle pour %s</string> <string name="snd_group_event_user_left">vous avez quitté</string> <string name="snd_group_event_group_profile_updated">mise à jour du profil de groupe</string> - <string name="rcv_conn_event_switch_queue_phase_completed">adresse modifiée pour vous</string> + <string name="rcv_conn_event_switch_queue_phase_completed">changement de l\'adresse du contact</string> <string name="snd_conn_event_switch_queue_phase_changing_for_member">changement d\'adresse pour %s…</string> <string name="snd_conn_event_switch_queue_phase_changing">changement d\'adresse…</string> <string name="group_member_role_member">membre</string> @@ -1117,10 +1117,10 @@ <string name="opening_database">Ouverture de la base de données…</string> <string name="learn_more_about_address">À propos de l\'adresse SimpleX</string> <string name="learn_more">En savoir plus</string> - <string name="you_can_share_your_address">Vous pouvez partager votre adresse sous la forme d\'un lien ou d\'un code QR - tout le monde peut se connecter à vous.</string> + <string name="you_can_share_your_address">Vous pouvez partager votre adresse sous la forme d\'un lien ou d\'un code QR - tout le monde peut l\'utiliser pour vous contacter.</string> <string name="you_wont_lose_your_contacts_if_delete_address">Vous ne perdrez pas vos contacts si vous supprimez votre adresse ultérieurement.</string> <string name="simplex_address">Adresse SimpleX</string> - <string name="you_can_accept_or_reject_connection">Lorsque des personnes demandent à se connecter, vous pouvez les accepter ou les refuser.</string> + <string name="you_can_accept_or_reject_connection">Vous pouvez accepter ou refuser les demandes de contacts.</string> <string name="theme_colors_section_title">COULEURS DU THÈME</string> <string name="your_contacts_will_remain_connected">Vos contacts resteront connectés.</string> <string name="share_address_with_contacts_question">Partager l\'adresse avec vos contacts \?</string> @@ -1155,7 +1155,7 @@ <string name="customize_theme_title">Personnaliser le thème</string> <string name="continue_to_next_step">Continuer</string> <string name="error_setting_address">Erreur lors du réglage de l\'adresse</string> - <string name="create_address_and_let_people_connect">Créez une adresse pour permettre aux gens de vous contacter.</string> + <string name="create_address_and_let_people_connect">Vous pouvez créer une adresse pour permettre aux autres utilisateurs de vous contacter.</string> <string name="enter_welcome_message">Entrez un message de bienvenue…</string> <string name="you_can_create_it_later">Vous pouvez la créer plus tard</string> <string name="email_invite_body">Bonjour ! @@ -1282,7 +1282,7 @@ <string name="snd_conn_event_ratchet_sync_required">renégociation de chiffrement requise pour %s</string> <string name="renegotiate_encryption">Renégocier le chiffrement</string> <string name="receipts_contacts_override_disabled">L\'envoi d\'accusés de réception est désactivé pour les contacts de %d</string> - <string name="snd_conn_event_ratchet_sync_started">accord sur le chiffrement pour %s…</string> + <string name="snd_conn_event_ratchet_sync_started">négociation du chiffrement avec %s…</string> <string name="receipts_section_contacts">Contacts</string> <string name="receipts_contacts_disable_for_all">Désactiver pour tous</string> <string name="receipts_contacts_disable_keep_overrides">Désactiver (conserver les remplacements)</string> @@ -1305,7 +1305,7 @@ <string name="receipts_contacts_title_enable">Activer les accusés de réception \?</string> <string name="receipts_contacts_title_disable">Désactiver les accusés de réception \?</string> <string name="receipts_contacts_override_enabled">L\'envoi d\'accusés de réception est activé pour les contacts de %d</string> - <string name="conn_event_ratchet_sync_started">accord sur le chiffrement…</string> + <string name="conn_event_ratchet_sync_started">négociation du chiffrement…</string> <string name="conn_event_ratchet_sync_agreed">chiffrement accepté</string> <string name="conn_event_ratchet_sync_ok">chiffrement OK</string> <string name="conn_event_ratchet_sync_allowed">renégociation de chiffrement autorisée</string> @@ -1398,7 +1398,7 @@ <string name="compose_send_direct_message_to_connect">Envoyer un message direct pour vous connecter</string> <string name="member_contact_send_direct_message">envoyer un message direct</string> <string name="rcv_group_event_member_created_contact">s\'est connecté.e de manière directe</string> - <string name="expand_verb">Développer</string> + <string name="expand_verb">Étendre</string> <string name="connect_plan_repeat_connection_request">Répéter la demande de connexion ?</string> <string name="rcv_direct_event_contact_deleted">contact supprimé</string> <string name="connect_plan_you_are_already_connecting_to_vName"><![CDATA[Vous êtes déjà connecté(e) à <b>%1$s</b>.]]></string> @@ -1612,9 +1612,107 @@ <string name="profile_update_event_member_name_changed">le membre %1$s est devenu %2$s</string> <string name="profile_update_event_removed_address">suppression de l\'adresse de contact</string> <string name="profile_update_event_removed_picture">suppression de la photo de profil</string> - <string name="profile_update_event_set_new_address">définir une nouvelle adresse de contact</string> - <string name="profile_update_event_set_new_picture">définir une nouvelle image de profil</string> + <string name="profile_update_event_set_new_address">a changé d\'adresse de contact</string> + <string name="profile_update_event_set_new_picture">a changé d\'image de profil</string> <string name="profile_update_event_updated_profile">profil mis à jour</string> <string name="clear_note_folder_question">Effacer les notes privées ?</string> <string name="saved_message_title">Message enregistré</string> + <string name="block_for_all">Bloqué pour tous</string> + <string name="member_blocked_by_admin">Bloqué par l\'administrateur</string> + <string name="member_info_member_blocked">bloqué</string> + <string name="error_blocking_member_for_all">Erreur lors du blocage du membre pour tous</string> + <string name="blocked_by_admin_items_description">%d messages bloqués par l\'administrateur</string> + <string name="blocked_by_admin_item_description">bloqué par l\'administrateur</string> + <string name="rcv_group_event_member_blocked">%s bloqué</string> + <string name="rcv_group_event_member_unblocked">%s débloqué</string> + <string name="snd_group_event_member_blocked">vous avez bloqué %s</string> + <string name="snd_group_event_member_unblocked">vous avez débloqué %s</string> + <string name="message_too_large">Message trop volumineux</string> + <string name="unblock_for_all">Débloquer pour tous</string> + <string name="block_for_all_question">Bloquer le membre pour tous ?</string> + <string name="database_migration_in_progress">La migration de la base de données est en cours. +\nCela peut prendre quelques minutes.</string> + <string name="unblock_for_all_question">Débloquer le membre pour tous ?</string> + <string name="welcome_message_is_too_long">Le message de bienvenue est trop long</string> + <string name="call_service_notification_video_call">Appel vidéo</string> + <string name="call_service_notification_end_call">Fin de l\'appel</string> + <string name="call_service_notification_audio_call">Appel audio</string> + <string name="unable_to_open_browser_desc">Le navigateur web par défaut est requis pour les appels. Veuillez configurer le navigateur par défaut dans le système et partager plus d\'informations avec les développeurs.</string> + <string name="unable_to_open_browser_title">Erreur lors de l\'ouverture du navigateur</string> + <string name="migrate_from_device_confirm_you_remember_passphrase">Confirmer que vous vous souvenez de la phrase secrète de la base de données pour la transférer.</string> + <string name="v5_6_app_data_migration">Transfert des données de l\'application</string> + <string name="v5_6_safer_groups_descr">Les admins peuvent bloquer un membre pour tous.</string> + <string name="migrate_from_device_exported_file_doesnt_exist">Le fichier exporté n\'existe pas</string> + <string name="migrate_from_device_error_uploading_archive">Erreur lors de l\'envoi de l\'archive</string> + <string name="migrate_from_device_all_data_will_be_uploaded">Tous vos contacts, conversations et fichiers seront chiffrés en toute sécurité et transférés par morceaux vers les relais XFTP configurés.</string> + <string name="migrate_to_device_apply_onion">Appliquer</string> + <string name="migrate_from_device_archive_and_upload">Archiver et transférer</string> + <string name="migrate_from_device_archiving_database">Archivage de la base de données</string> + <string name="migrate_from_device_archive_will_be_deleted"><![CDATA[<b>Avertissement</b> : l\'archive sera supprimée.]]></string> + <string name="migrate_from_device_using_on_two_device_breaks_encryption"><![CDATA[<b>Remarque</b> : l\'utilisation d\'une même base de données sur deux appareils interrompra le déchiffrement des messages provenant de vos connexions, par mesure de sécurité.]]></string> + <string name="migrate_from_device_cancel_migration">Annuler le transfert</string> + <string name="migrate_to_device_chat_migrated">Messagerie transférée !</string> + <string name="migrate_from_device_check_connection_and_try_again">Vérifiez votre connexion internet et réessayez</string> + <string name="migrate_to_device_download_failed">Échec du téléchargement</string> + <string name="migrate_from_device_choose_migrate_from_another_device"><![CDATA[Sélectionnez <i>Transférer depuis un autre appareil</i> sur le nouvel appareil et scanner le code QR.]]></string> + <string name="migrate_to_device_confirm_network_settings">Confirmer les paramètres réseau</string> + <string name="migrate_from_device_confirm_upload">Confirmer la transmission</string> + <string name="migrate_from_device_creating_archive_link">Création d\'un lien d\'archive</string> + <string name="migrate_from_device_delete_database_from_device">Supprimer la base de données de cet appareil</string> + <string name="migrate_to_device_downloading_archive">Téléchargement de l\'archive</string> + <string name="migrate_to_device_downloading_details">Téléchargement des détails du lien</string> + <string name="v5_6_quantum_resistant_encryption_descr">Activé dans les conversations directes (BETA) !</string> + <string name="migrate_to_device_enter_passphrase">Entrer la phrase secrète</string> + <string name="migrate_from_device_error_deleting_database">Erreur lors de la suppression de la base de données</string> + <string name="migrate_from_device_error_exporting_archive">Erreur lors de l\'exportation de la base de données des chats</string> + <string name="migrate_to_device_error_downloading_archive">Erreur lors du téléchargement de l\'archive</string> + <string name="migrate_from_device_error_saving_settings">Erreur lors de l\'enregistrement des paramètres</string> + <string name="migrate_from_device_error_verifying_passphrase">Erreur lors de la vérification de la phrase secrète :</string> + <string name="e2ee_info_no_pq"><![CDATA[Les messages, fichiers et appels sont protégés par un <b>chiffrement de bout en bout</b> avec une confidentialité persistante, une répudiation et une récupération en cas d\'effraction.]]></string> + <string name="e2ee_info_pq"><![CDATA[Les messages, fichiers et appels sont protégés par un <b>chiffrement e2e résistant post-quantique</b> avec une confidentialité persistante, une répudiation et une récupération en cas d\'effraction.]]></string> + <string name="e2ee_info_no_pq_short">Cette discussion est protégée par un chiffrement de bout en bout.</string> + <string name="e2ee_info_pq_short">Cette discussion est protégée par un chiffrement de bout en bout résistant post-quantique.</string> + <string name="auth_open_migration_to_another_device">Accéder à l\'écran de transfert</string> + <string name="migrate_from_another_device">Transférer depuis un autre appareil</string> + <string name="set_passphrase">Définir une phrase secrète</string> + <string name="conn_event_enabled_pq">chiffrement e2e résistant post-quantique</string> + <string name="migrate_from_device_upload_failed">Échec de l\'envoi</string> + <string name="migrate_from_device_try_again">Vous pouvez faire un nouvel essai.</string> + <string name="migrate_from_device_repeat_upload">Répéter l\'envoi</string> + <string name="v5_6_app_data_migration_descr">Transférer vers un autre appareil via un code QR.</string> + <string name="v5_6_quantum_resistant_encryption">Chiffrement résistant post-quantique</string> + <string name="v5_6_picture_in_picture_calls">Appels picture-in-picture</string> + <string name="v5_6_safer_groups">Groupes plus sûrs</string> + <string name="v5_6_picture_in_picture_calls_descr">Utiliser l\'application pendant l\'appel.</string> + <string name="migrate_to_device_database_init">Préparation du téléchargement</string> + <string name="migrate_to_device_bytes_downloaded">%s téléchargés</string> + <string name="migrate_to_device_importing_archive">Importation de l\'archive</string> + <string name="migrate_to_device_repeat_import">Répéter l\'importation</string> + <string name="migrate_to_device_finalize_migration">Finalisez le transfert sur l\'autre appareil.</string> + <string name="migrate_from_device_title">Transférer l\'appareil</string> + <string name="migrate_from_device_to_another_device">Transférer vers un autre appareil</string> + <string name="migrate_from_device_database_init">Préparation de l\'envoi</string> + <string name="migrate_from_device_chat_should_be_stopped">Pour continuer, le chat doit être interrompu.</string> + <string name="migrate_from_device_stopping_chat">Arrêt du chat</string> + <string name="migrate_from_device_bytes_uploaded">%s envoyé</string> + <string name="migrate_from_device_uploading_archive">Envoi de l\'archive</string> + <string name="migrate_from_device_finalize_migration">Finaliser le transfert</string> + <string name="migrate_from_device_migration_complete">Transfert terminé</string> + <string name="migrate_from_device_start_chat">Démarrer le chat</string> + <string name="migrate_from_device_you_must_not_start_database_on_two_device"><![CDATA[Vous <b>ne devez pas</b> utiliser la même base de données sur deux appareils.]]></string> + <string name="migrate_from_device_verify_database_passphrase">Vérifier la phrase secrète de la base de données</string> + <string name="migrate_to_device_repeat_download">Répéter le téléchargement</string> + <string name="migrate_to_device_confirm_network_settings_footer">Veuillez confirmer que les paramètres réseau de cet appareil sont corrects.</string> + <string name="conn_event_disabled_pq">chiffrement de bout en bout standard</string> + <string name="migrate_to_device_file_delete_or_link_invalid">Le fichier a été supprimé ou le lien est invalide</string> + <string name="migrate_to_device_import_failed">Échec de l\'importation</string> + <string name="invalid_file_link">Lien invalide</string> + <string name="migrate_to_device_title">Transférer ici</string> + <string name="migrate_to_device_migrating">Transfert</string> + <string name="or_paste_archive_link">Ou coller le lien de l\'archive</string> + <string name="migrate_from_device_or_share_this_file_link">Ou partagez en toute sécurité le lien de ce fichier</string> + <string name="paste_archive_link">Coller le lien de l\'archive</string> + <string name="migrate_from_device_verify_passphrase">Vérifier la phrase secrète</string> + <string name="migrate_from_device_starting_chat_on_multiple_devices_unsupported">Attention : démarrer une session de chat sur plusieurs appareils n\'est pas pris en charge et entraînera des dysfonctionnements au niveau de la transmission des messages</string> + <string name="migrate_to_device_try_again">Vous pouvez faire un nouvel essai.</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index 4c8ceed529..f53d4d9802 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -6,179 +6,179 @@ <string name="group_info_section_title_num_members">%1$s TAG</string> <string name="chat_item_ttl_month">1 hónap</string> <string name="chat_item_ttl_week">1 hét</string> - <string name="v5_3_new_interface_languages">6 új kezelőfelület nyelv</string> + <string name="v5_3_new_interface_languages">6 új felületi nyelv</string> <string name="send_disappearing_message_5_minutes">5 perc</string> <string name="send_disappearing_message_1_minute">1 perc</string> - <string name="learn_more_about_address">A SimpleX címről</string> + <string name="learn_more_about_address">A SimpleX azonosítóról</string> <string name="abort_switch_receiving_address_question">Címváltoztatás megszakítása?</string> <string name="abort_switch_receiving_address_confirm">Megszakítás</string> <string name="send_disappearing_message_30_seconds">30 másodperc</string> - <string name="one_time_link_short">Egyszer használatos link</string> - <string name="contact_wants_to_connect_via_call">%1$s szeretne kapcsolatba lépni veled</string> - <string name="about_simplex_chat">A SimpleX chatről</string> + <string name="one_time_link_short">Egyszer használatos hivatkozás</string> + <string name="contact_wants_to_connect_via_call">%1$s szeretne kapcsolatba lépni önnel ezen keresztül:</string> + <string name="about_simplex_chat">A SimpleX Chat névjegye</string> <string name="chat_item_ttl_day">1 nap</string> <string name="abort_switch_receiving_address">Címváltoztatás megszakítása</string> - <string name="about_simplex">A SimpleX-ről</string> + <string name="about_simplex">A SimpleX névjegye</string> <string name="color_primary">Kiemelés</string> - <string name="callstatus_accepted">elfogadott hívás</string> - <string name="network_enable_socks_info">Kapcsolódás a szerverekhez SOCKS proxy segítségével a %d porton? A proxyt el kell indítani mielőtt bekapcsolnád ezt az opciót.</string> - <string name="accept_feature">Elfogad</string> - <string name="accept_call_on_lock_screen">Elfogad</string> + <string name="callstatus_accepted">fogadott hívás</string> + <string name="network_enable_socks_info">Hozzáférés a kiszolgálókhoz SOCKS proxy segítségével a %d porton? A proxyt el kell indítani, mielőtt engedélyezné ezt az opciót.</string> + <string name="accept_feature">Elfogadás</string> + <string name="accept_call_on_lock_screen">Elfogadás</string> <string name="above_then_preposition_continuation">fentről, utána:</string> - <string name="accept_contact_incognito_button">Elfogadás inkognítóban</string> - <string name="accept_connection_request__question">Elfogadod a kapcsolatfelvételt?</string> - <string name="accept_contact_button">Elfogad</string> - <string name="accept">Elfogad</string> - <string name="add_address_to_your_profile">Add hozzá a címet a profilodhoz, így a kapcsolataid megoszthatják azt más emberekkel. A profilod változtatásai így frissítésre kerülnek a kapcsolataidnál is!</string> + <string name="accept_contact_incognito_button">Fogadás inkognítóban</string> + <string name="accept_connection_request__question">Kapcsolatfelvétel elfogadása?</string> + <string name="accept_contact_button">Elfogadás</string> + <string name="accept">Elfogadás</string> + <string name="add_address_to_your_profile">Azonosító hozzáadása a profilhoz, hogy az ismerősei megoszthassák másokkal. A profilfrissítés elküldésre kerül az ismerősök számára.</string> <string name="color_primary_variant">További kiemelés</string> <string name="callstatus_error">hiba a hívásban</string> - <string name="v5_4_block_group_members">Csoporttagok blokkolása</string> + <string name="v5_4_block_group_members">Csoporttagok letiltása</string> <string name="la_authenticate">Hitelesítés</string> - <string name="empty_chat_profile_is_created">Egy üres chat profil létre lett hozva a megadott névvel és az app normál módon megnyílik.</string> + <string name="empty_chat_profile_is_created">Egy üres csevegési profil jön létre a megadott névvel, és az alkalmazás a szokásos módon megnyílik.</string> <string name="feature_cancelled_item">megszakítva %s</string> - <string name="smp_servers_preset_add">Adj hozzá egyedi szervereket</string> + <string name="smp_servers_preset_add">Előre beállított kiszolgálók hozzáadása</string> <string name="calls_prohibited_with_this_contact">A hang- és videóhívások le vannak tiltva.</string> - <string name="network_session_mode_entity_description">Külön TCP kapcsolat (és SOCKS bejelentkezési adatok) lesznek használva <b>minden ismerősre és csoport tagra</b> -\n<b>Tudnivaló</b>: ha sok ismerősöd van, az akkumulátor- és adat használatod jelentősen megnőhet és néhány kapcsolódási kísérlet sikertelen lehet.</string> - <string name="icon_descr_cancel_link_preview">URL link előnézet megszakítása</string> - <string name="network_session_mode_user_description"><![CDATA[Külön TCP kapcsolat (és SOCKS bejelentkezési adatok) lesznek használva <b>minden chat profilodra az appban</b>.]]></string> - <string name="both_you_and_your_contact_can_send_disappearing">Mindketten, te is és az ismerősöd is küldhet eltűnő üzeneteket.</string> + <string name="network_session_mode_entity_description">Külön TCP kapcsolat (és SOCKS bejelentkezési adatok) lesz használva <b>minden ismerős és csoporttag számára</b>. +\n<b>Figyelem</b>: ha sok ismerőse van, az akkumulátor- és adathasználat jelentősen megnövekedhet és néhány kapcsolódási kísérlet sikertelen lehet.</string> + <string name="icon_descr_cancel_link_preview">Hivatkozás előnézet megszakítása</string> + <string name="network_session_mode_user_description"><![CDATA[Külön TCP kapcsolat (és SOCKS bejelentkezési adatok) lesz használva <b>az alkalmazásban minden csevegési profiljához </b>.]]></string> + <string name="both_you_and_your_contact_can_send_disappearing">Mindkét fél küldhet eltűnő üzeneteket.</string> <string name="keychain_is_storing_securely">Az Android Keystore-t a jelmondat biztonságos tárolására használják - lehetővé teszi az értesítési szolgáltatás működését.</string> <string name="alert_title_msg_bad_hash">Téves üzenet hash</string> <string name="color_background">Háttér</string> - <string name="socks_proxy_setting_limitations"><![CDATA[<b>Tudnivaló</b>: az üzenet- és fájl relay szerverek SOCKS proxy által vannak kapcsolatban. A hívások és URL link előnézetek közvetlen kapcsolatot használnak.]]></string> - <string name="full_backup">App adatmentés</string> + <string name="socks_proxy_setting_limitations"><![CDATA[<b>Tudnivaló</b>: az üzenet- és fájl átjátszók SOCKS proxy által vannak kapcsolatban. A hívások és URL hivatkozás előnézetek 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 inicializálása sikertelen</string> - <string name="all_your_contacts_will_remain_connected_update_sent">A kapcsolat megmarad az összes Ismerősöddel. Profil változtatások frissítésre kerülnek az ismerőseidnél.</string> - <string name="v4_5_transport_isolation_descr">Chat profile (alap beállítás) avagy kapcsolat által (BÉTA).</string> + <string name="all_your_contacts_will_remain_connected_update_sent">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 profil által (alap beállítás), vagy a kapcsolat által (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">Hangüzenetek küldésének engedélyezése kizárólag abban az esetben, ha a másik fél is engedélyezi.</string> + <string name="allow_voice_messages_only_if">Hangüzenetek küldésének engedélyezése kizárólag abban az esetben, ha az ismerőse is engedélyezi.</string> <string name="app_version_code">Az alkalmazás build száma: %s</string> <string name="audio_video_calls">Hang-/videóhívások</string> - <string name="network_settings">Haladó hálózati beállítások</string> - <string name="allow_your_contacts_to_send_voice_messages">Ismerőseid küldhetnek hangüzeneteket.</string> + <string name="network_settings">Speciális hálózati beállítások</string> + <string name="allow_your_contacts_to_send_voice_messages">Hangüzenetek küldésének engedélyezése az ismerősei számára.</string> <string name="settings_audio_video_calls">Hang- és videóhívások</string> - <string name="v5_3_encrypt_local_files_descr">Az app titkosítja a helyi fájlokat (a videók kivételével).</string> + <string name="v5_3_encrypt_local_files_descr">Az alkalmazás titkosítja a helyi fájlokat (a videók kivételével).</string> <string name="answer_call">Hívás fogadása</string> - <string name="allow_your_contacts_to_send_disappearing_messages">Ismerőseid küldhetnek eltűnő üzeneteket.</string> - <string name="connect_plan_already_connecting">Már kapcsolódik!</string> - <string name="cannot_receive_file">Nem tud fájlt fogadni</string> + <string name="allow_your_contacts_to_send_disappearing_messages">Eltűnő üzenetek engedélyezése az ismerősei számára.</string> + <string name="connect_plan_already_connecting">Kapcsolódás folyamatban!</string> + <string name="cannot_receive_file">Nem lehet fogadni a fájlt</string> <string name="auth_unavailable">Hitelesítés elérhetetlen</string> <string name="app_version_title">Alkalmazás verzió</string> <string name="button_add_welcome_message">Üdvözlő üzenet hozzáadása</string> <string name="snd_conn_event_ratchet_sync_started">a(z) %s titkosításának elfogadása…</string> <string name="available_in_v51">" \nElérhető a v5.1-ben"</string> - <string name="both_you_and_your_contacts_can_delete">Mindketten, te is és az ismerősöd is véglegesen törölhet elküldött üzeneteket. (24 óra)</string> + <string name="both_you_and_your_contacts_can_delete">Mindkét fél visszafordíthatatlanul törölheti az elküldött üzeneteket. (24 óra)</string> <string name="v5_4_better_groups">Javított csoportok</string> - <string name="clear_chat_warning">Minden üzenet törlésre kerül - ezt nem lehet visszavonni! Az üzenetek CSAK nálad törlődnek.</string> + <string name="clear_chat_warning">Minden üzenet törlésre kerül - ezt nem vonható vissza! Az üzenetek CSAK az ön számára törlődnek.</string> <string name="icon_descr_call_ended">Hívás befejeződött</string> <string name="settings_section_title_calls">HÍVÁSOK</string> <string name="rcv_group_and_other_events">és %d egyéb esemény</string> <string name="address_section_title">Cím</string> - <string name="connect_plan_already_joining_the_group">Már csatlakozik a csoporthoz!</string> + <string name="connect_plan_already_joining_the_group">Csatlakozás folyamatban!</string> <string name="auto_accept_contact">Automatikus elfogadás</string> <string name="notifications_mode_service_desc">A háttérszolgáltatás mindig fut - az értesítések azonnal megjelennek, amint üzenetek vannak.</string> - <string name="allow_to_delete_messages">Elküldött üzenetek végleges törlésének engedélyezése. (24 óra)</string> - <string name="both_you_and_your_contact_can_send_voice">Mindketten, te is és az ismerősöd is küldhet hangüzeneteket.</string> + <string name="allow_to_delete_messages">Az elküldött üzenetek visszafordíthatatlan törlése engedélyezve van. (24 óra)</string> + <string name="both_you_and_your_contact_can_send_voice">Mindkét fél küldhet hangüzeneteket.</string> <string name="alert_title_msg_bad_id">Téves üzenet ID</string> - <string name="allow_your_contacts_adding_message_reactions">Ismerőseid küldhetnek reakciókat az üzenetekre.</string> - <string name="allow_to_send_voice">Hangüzenetek küldése engedélyezve.</string> - <string name="allow_message_reactions_only_if">Üzenet reakciók engedélyezése kizárólag abban az esetben, ha a másik fél is engedélyezi.</string> + <string name="allow_your_contacts_adding_message_reactions">Ismerősök általi üzenetreakciók küldésének engedélyezése.</string> + <string name="allow_to_send_voice">A hangüzenetek küldése engedélyezve van.</string> + <string name="allow_message_reactions_only_if">Üzenetreakciók engedélyezése kizárólag abban az esetben, ha az ismerőse is engedélyezi.</string> <string name="back">Vissza</string> <string name="it_can_disabled_via_settings_notifications_still_shown"><![CDATA[<b>Kikapcsolható a beállításokban</b> – az értesítések továbbra is megjelenítésre kerülnek amíg az alkalmazás fut.]]></string> - <string name="v4_2_group_links_desc">Az adminok létrehozhatnak linkeket a csoporthoz való csatlakozáshoz.</string> - <string name="call_on_lock_screen">Hívások a lezárási képernyőn:</string> + <string name="v4_2_group_links_desc">Az adminok hivatkozásokat hozhatnak létre a csoportokhoz való csatlakozáshoz.</string> + <string name="call_on_lock_screen">Hívások a zárolási képernyőn:</string> <string name="conn_event_ratchet_sync_started">titkosítás elfogadása…</string> - <string name="invite_prohibited">Az ismerősök meghívása le van tiltva!</string> + <string name="invite_prohibited">Ismerősök meghívása le van tiltva!</string> <string name="integrity_msg_bad_id">téves üzenet ID</string> - <string name="v4_2_auto_accept_contact_requests">Ismerősnek jelölések automatikus elfogadása</string> - <string name="impossible_to_recover_passphrase"><![CDATA[<b>Tudnivaló</b>: NEM fogod tudni helyreállítani vagy megváltoztatni a jelmondatot az esetben ha elveszíted.]]></string> + <string name="v4_2_auto_accept_contact_requests">Ismerős jelölések automatikus elfogadása</string> + <string name="impossible_to_recover_passphrase"><![CDATA[<b>Figyelem</b>: NEM fogja tudni helyreállítani, vagy megváltoztatni a jelmondatot abban az esetben, ha elveszíti.]]></string> <string name="callstatus_calling">hívás…</string> <string name="color_secondary_variant">További másodlagos</string> - <string name="smp_servers_add_to_another_device">Hozzáadás másik eszközhöz</string> - <string name="allow_message_reactions">Üzenet reakciók engedélyezése.</string> + <string name="smp_servers_add_to_another_device">Hozzáadás egy másik eszközhöz</string> + <string name="allow_message_reactions">Az üzenetreakciók küldése engedélyezve van.</string> <string name="icon_descr_cancel_file_preview">Fájl előnézet megszakítása</string> - <string name="all_group_members_will_remain_connected">Minden csoporttag kapcsolatban marad.</string> - <string name="onboarding_notifications_mode_service_desc"><![CDATA[<b>Több akkumulátort használ</b>! Háttérszolgáltatás mindig fut - értesítések megjelennek azonnal, ahogy új üzenetek érkeznek.]]></string> - <string name="block_member_confirmation">Blokkolás</string> + <string name="all_group_members_will_remain_connected">Minden csoporttag csatlakoztatva marad.</string> + <string name="onboarding_notifications_mode_service_desc"><![CDATA[<b>Több akkumulátort használ</b>! Háttérszolgáltatás mindig fut - az értesítések megjelennek, amint az üzenetek elérhetővé válnak.]]></string> + <string name="block_member_confirmation">Letiltás</string> <string name="group_member_role_admin">admin</string> <string name="icon_descr_cancel_image_preview">Fénykép előnézet megszakítása</string> - <string name="v5_1_self_destruct_passcode_descr">Minden adat törlődik miután a jelkód bevitelre került.</string> - <string name="icon_descr_video_asked_to_receive">Felkértek a videó fogadására</string> - <string name="block_member_button">Tag blokkolása</string> - <string name="v5_2_more_things">Néhány további dolog</string> + <string name="v5_1_self_destruct_passcode_descr">A jelkód megadása után minden adat törlésre kerül.</string> + <string name="icon_descr_video_asked_to_receive">Felkérték a videó fogadására</string> + <string name="block_member_button">Tag letiltása</string> + <string name="v5_2_more_things">Még néhány dolog</string> <string name="authentication_cancelled">Hitelesítés megszakítva</string> - <string name="allow_to_send_files">A fájlok- és a médiatartalom küldése engedélyezve.</string> - <string name="users_delete_all_chats_deleted">Minden beszélgetés, illetve az összes üzenet törlésre kerül - ezt nem lehet visszavonni!</string> + <string name="allow_to_send_files">A fájlok- és a médiatartalom küldése engedélyezve van.</string> + <string name="users_delete_all_chats_deleted">Minden csevegés és üzenet törlésre kerül - ez nem vonható vissza!</string> <string name="icon_descr_audio_call">hanghívás</string> <string name="bold_text">félkövér</string> <string name="app_passcode_replaced_with_self_destruct">Az alkalmazás jelkód helyettesítésre kerül egy önmegsemmisítő jelkóddal.</string> <string name="v5_3_new_interface_languages_descr">Arab, bulgár, finn, héber, thai és ukrán - köszönet a felhasználóknak és a Weblate-nek!</string> - <string name="allow_voice_messages_question">Engedélyezed a hangüzenetek küldését?</string> - <string name="always_use_relay">Mindig használjon relay szervert</string> + <string name="allow_voice_messages_question">Hangüzenetek engedélyezése?</string> + <string name="always_use_relay">Mindig használjon átjátszó kiszolgálót</string> <string name="chat_preferences_always">mindig</string> <string name="call_already_ended">A hívás már befejeződött!</string> - <string name="turn_off_battery_optimization_button">Engedélyez</string> - <string name="all_your_contacts_will_remain_connected">A kapcsolat megmarad az összes Ismerősöddel.</string> - <string name="icon_descr_cancel_live_message">Élő chat üzenet megszakítása</string> - <string name="allow_irreversible_message_deletion_only_if">Üzenet végleges törlésének engedélyezése kizárólag abban az esetben, ha a másik fél is engedélyezi. (24 óra)</string> + <string name="turn_off_battery_optimization_button">Engedélyezés</string> + <string name="all_your_contacts_will_remain_connected">Minden ismerős csatlakoztatva marad.</string> + <string name="icon_descr_cancel_live_message">Élő csevegési üzenet megszakítása</string> + <string name="allow_irreversible_message_deletion_only_if">Üzenet végleges törlésének engedélyezése kizárólag abban az esetben, ha az ismerőse is engedélyezi. (24 óra)</string> <string name="v4_6_audio_video_calls">Hang- és videóhívások</string> <string name="integrity_msg_bad_hash">téves üzenet hash</string> <string name="notifications_mode_service">Mindig bekapcsolva</string> - <string name="keychain_allows_to_receive_ntfs">Az Android Keystore fogja biztonságosan tárolni a jelmondatot app újraindítás vagy jelmondat változtatás után - lehetővé téve az értesítések fogadását.</string> - <string name="all_app_data_will_be_cleared">Minden alkalmazás adat törölve.</string> - <string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>Legjobb akkumulátoridő</b>. Kizárólag akkor kapsz értesítéseket amikor fut az app (NINCS háttérszolgáltatás).]]></string> + <string name="keychain_allows_to_receive_ntfs">Az Android Keystore biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat megváltoztatás után - lehetővé téve az értesítések fogadását.</string> + <string name="all_app_data_will_be_cleared">Minden alkalmazásadat törölve.</string> + <string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>Legjobb akkumulátoridő</b>. Csak akkor kap értesítést, ha az alkalmazás fut (NINCS háttérszolgáltatás).]]></string> <string name="appearance_settings">Megjelenés</string> - <string name="turning_off_service_and_periodic">Akkumulátor optimizáció aktív, a háttérszolgáltatás és a rendszeres új üzenet ellenőrzés kikapcsolva. Újra bekapcsolhatod ezeket a beállításokban.</string> - <string name="block_member_question">Tag blokkolása?</string> - <string name="callstatus_ended">a(z) %1$s hívás befejeződött</string> - <string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b>Jó akkumulátoridő</b>. A háttérszolgáltatás ellenőrzi az új üzeneteket 10 percenként. Hívásokról és fontos üzenetekről maradhatsz le.]]></string> + <string name="turning_off_service_and_periodic">Az akkumulátor optimalizálása aktív, mely kikapcsolja a háttérszolgáltatást és az új üzenetek rendszeres kérését. A beállításokon keresztül újra engedélyezhetők.</string> + <string name="block_member_question">Tag letiltása?</string> + <string name="callstatus_ended">%1$s hívása befejeződött</string> + <string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b>Jó akkumulátoridő</b>. A háttérszolgáltatás 10 percenként ellenőrzi az új üzeneteket. Előfordulhat, hogy hívásokról, vagy a sürgős üzenetekről marad le.]]></string> <string name="group_member_role_author">szerző</string> - <string name="allow_your_contacts_irreversibly_delete">Engedélyezed az ismerőseidnek, hogy visszafordíthatatlanul törölhessék az elküldött üzeneteket. (24 óra)</string> + <string name="allow_your_contacts_irreversibly_delete">Elküldött üzenetek visszafordíthatatlan törlésének engedélyezése az ismerősei számára. (24 óra)</string> <string name="cancel_verb">Megszakítás</string> - <string name="notifications_mode_off_desc">Az alkalmazás csak akkor tud értesítéseket fogadni amikor fut, a héttérszolgáltatás nem kerül elindításra.</string> + <string name="notifications_mode_off_desc">Az alkalmazás csak akkor tud értesítéseket fogadni amikor fut, háttérszolgáltatás nem indul el</string> <string name="v5_1_better_messages">Jobb üzenetek</string> - <string name="abort_switch_receiving_address_desc">A cím változtatás megszakításra kerül. A régi fogadó cím marad használatban.</string> - <string name="allow_verb">Engedélyez</string> - <string name="bad_desktop_address">Téves számítógép cím</string> + <string name="abort_switch_receiving_address_desc">A cím módosítása megszakad. A régi fogadási cím kerül felhasználásra.</string> + <string name="allow_verb">Engedélyezés</string> + <string name="bad_desktop_address">Hibás számítógép-azonosító</string> <string name="users_add">Profil hozzáadása</string> <string name="attach">Csatolás</string> <string name="v5_0_app_passcode">Alkalmazás jelkód</string> - <string name="icon_descr_asked_to_receive">Felkértek a kép fogadására</string> + <string name="icon_descr_asked_to_receive">Felkérték a kép fogadására</string> <string name="use_camera_button">Fényképező</string> - <string name="cannot_access_keychain">A Keystore-hoz nem sikerül hozzáférni az adatbázis jelszó elmentése végett</string> + <string name="cannot_access_keychain">A Keystore-hoz nem sikerül hozzáférni az adatbázis jelszó mentése végett</string> <string name="callstatus_in_progress">hívás folyamatban</string> <string name="auto_accept_images">Fotók automatikus elfogadása</string> - <string name="allow_your_contacts_to_call">Hang- és videóhívás engedélyezése az ismerőseid számára.</string> + <string name="allow_your_contacts_to_call">Hívások engedélyezése az ismerősei számára.</string> <string name="settings_section_title_icon">ALKALMAZÁS IKON</string> - <string name="v4_3_improved_server_configuration_desc">Szerver hozzáadása QR kód befotózásával.</string> - <string name="allow_to_send_disappearing">Eltűnő üzenetek küldése engedélyezve.</string> - <string name="allow_disappearing_messages_only_if">Eltűnő üzenetek engedélyezése kizárólag abban az esetben, ha a másik fél is engedélyezi.</string> + <string name="v4_3_improved_server_configuration_desc">Kiszolgáló hozzáadása QR-kód beolvasásával.</string> + <string name="allow_to_send_disappearing">Az eltűnő üzenetek küldése engedélyezve van.</string> + <string name="allow_disappearing_messages_only_if">Eltűnő üzenetek engedélyezése kizárólag abban az esetben, ha az ismerőse is engedélyezi.</string> <string name="icon_descr_audio_off">Hang kikapcsolva</string> - <string name="allow_direct_messages">A közvetlen üzenetküldés csak a tagok számára engedélyezett.</string> + <string name="allow_direct_messages">A közvetlen üzenetek küldése a tagok számára engedélyezve van.</string> <string name="settings_section_title_app">Alkalmazás</string> <string name="icon_descr_call_progress">Hívás folyamatban</string> - <string name="both_you_and_your_contact_can_add_message_reactions">Mindketten, te is és az ismerősöd is használhat üzenet reakciókat (emojik).</string> - <string name="both_you_and_your_contact_can_make_calls">Mindketten, te is és az ismerősöd is tud hívásokat indítani.</string> + <string name="both_you_and_your_contact_can_add_message_reactions">Mindkét fél küldhet üzenetreakciókat.</string> + <string name="both_you_and_your_contact_can_make_calls">Mindkét fél tud hívásokat indítani.</string> <string name="la_auth_failed">Hitelesítés sikertelen</string> - <string name="block_member_desc">Minden új üzenet %s -tól/től elrejtésre kerül.</string> + <string name="block_member_desc">Minden %s által írt új üzenet elrejtésre kerül!</string> <string name="app_version_name">Alkalmazás verzió: v%s</string> - <string name="allow_calls_only_if">Hívások engedélyezése kizárólag abban az esetben, ha a másik fél is engedélyezi.</string> - <string name="smp_servers_add">Szerver hozzáadása…</string> + <string name="allow_calls_only_if">Hívások engedélyezése kizárólag abban az esetben, ha az ismerőse is engedélyezi.</string> + <string name="smp_servers_add">Kiszolgáló hozzáadása…</string> <string name="icon_descr_audio_on">Hang bekapcsolva</string> <string name="audio_call_no_encryption">hanghívás (nem e2e titkosított)</string> - <string name="blocked_item_description">blokkolva</string> + <string name="blocked_item_description">letiltva</string> <string name="change_database_passphrase_question">Adatbázis jelmondat megváltoztatása?</string> <string name="callstate_connected">kapcsolódva</string> <string name="la_change_app_passcode">Jelkód megváltoztatása</string> - <string name="rcv_group_event_changed_member_role">a szerepkör %s -ról(-ről), %s -ra(-re) változott</string> - <string name="switch_receiving_address">A fogadó szerver címének megváltoztatása</string> + <string name="rcv_group_event_changed_member_role">%s szerepköre megváltozott erre: %s</string> + <string name="switch_receiving_address">A fogadó cím megváltoztatása</string> <string name="change_verb">Változtatás</string> <string name="confirm_passcode">Jelkód megerősítése</string> <string name="confirm_password">Jelszó megerősítése</string> <string name="change_member_role_question">Csoport szerepkör megváltoztatása?</string> - <string name="change_lock_mode">Lezárási mód megváltoztatása</string> + <string name="change_lock_mode">Zárolási mód megváltoztatása</string> <string name="notification_contact_connected">Kapcsolódva</string> <string name="rcv_group_event_member_connected">kapcsolódva</string> <string name="connect_via_link_verb">Kapcsolódás</string> @@ -188,35 +188,35 @@ <string name="change_role">Szerepkör megváltoztatása</string> <string name="icon_descr_server_status_connected">Kapcsolódva</string> <string name="auth_confirm_credential">Belépési adatok megerősítése</string> - <string name="switch_receiving_address_question">Fogadó szerver cím megváltoztatása?</string> - <string name="rcv_conn_event_switch_queue_phase_completed">megváltozott az azonosító számodra</string> + <string name="switch_receiving_address_question">Megváltoztatja a fogadó címet?</string> + <string name="rcv_conn_event_switch_queue_phase_completed">Cím megváltoztatva</string> <string name="change_self_destruct_mode">Önmegsemmisítő mód megváltoztatása</string> - <string name="rcv_group_event_changed_your_role">a szerepköröd megváltoztatva %s-ra(-re)</string> + <string name="rcv_group_event_changed_your_role">megváltoztatta az ön szerepkörét erre: %s</string> <string name="connect_button">Kapcsolódás</string> - <string name="connect_via_member_address_alert_title">Kapcsolódás közvetlenül?</string> + <string name="connect_via_member_address_alert_title">Közvetlen kapcsolódás?</string> <string name="smp_server_test_connect">Kapcsolódás</string> <string name="rcv_group_event_member_created_contact">közvetlenül kapcsolódva</string> <string name="connection_local_display_name">kapcsolat %1$d</string> - <string name="status_contact_has_e2e_encryption">az ismerősnél az e2e titkosítás elérhető</string> - <string name="v5_4_incognito_groups_descr">Csoport létrehozása véletlenszerűen létrehozott profillal.</string> - <string name="delete_contact_all_messages_deleted_cannot_undo_warning">Az ismerős és az összes üzenet törlésre kerül - ez visszafordíthatatlan!</string> - <string name="contacts_can_mark_messages_for_deletion">Ismerősök megjelölhetik az üzeneteket törlendőként; de te láthatod azokat.</string> - <string name="connect_via_invitation_link">Kapcsolódás Egyszer használatos linkkel?</string> - <string name="connect_via_link_or_qr">Kapcsolódás egy link / QR-kód által</string> + <string name="status_contact_has_e2e_encryption">az ismerős e2e titkosítással rendelkezik</string> + <string name="v5_4_incognito_groups_descr">Csoport létrehozása véletlenszerű profillal.</string> + <string name="delete_contact_all_messages_deleted_cannot_undo_warning">Az ismerős és az összes üzenet törlésre kerül - ez nem vonható vissza!</string> + <string name="contacts_can_mark_messages_for_deletion">Az ismerősök törlésre jelölhetnek üzeneteket ; megtekintheti őket.</string> + <string name="connect_via_invitation_link">Kapcsolódás egyszer használatos hivatkozással?</string> + <string name="connect_via_link_or_qr">Kapcsolódás egy hivatkozás / QR-kód által</string> <string name="connection_error_auth">Kapcsolódási hiba (AUTH)</string> <string name="notification_preview_mode_contact">Ismerős neve</string> - <string name="connect_via_contact_link">Kapcsolódás ismerős azonosítója által?</string> - <string name="create_address">Cím létrehozása</string> + <string name="connect_via_contact_link">Kapcsolódik ehhez az ismerőshöz?</string> + <string name="create_address">Azonosító létrehozása</string> <string name="copy_verb">Másolás</string> <string name="continue_to_next_step">Folytatás</string> - <string name="connect_plan_connect_via_link">Kapcsolódás egy linken keresztül?</string> - <string name="contact_already_exists">Az ismerős már létezik</string> + <string name="connect_plan_connect_via_link">Kapcsolódás egy hivatkozáson keresztül?</string> + <string name="contact_already_exists">Létező ismerős</string> <string name="core_version">Fő verzió: v%s</string> <string name="icon_descr_contact_checked">Ismerős ellenőrizve</string> <string name="connect_plan_connect_to_yourself">Kapcsolódás saját magához?</string> <string name="copied">Kimásolva a vágólapra</string> <string name="connection_request_sent">Kapcsolódási kérés elküldve!</string> - <string name="connecting_to_desktop">Kapcsolódás az asztali klienshez</string> + <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> @@ -226,153 +226,153 @@ <string name="info_row_connection">Kapcsolat</string> <string name="desktop_connection_terminated">Kapcsolat megszakítva</string> <string name="display_name_connection_established">Kapcsolat létrehozva</string> - <string name="status_contact_has_no_e2e_encryption">az ismerősnél az e2e titkosítás nem elérhető</string> - <string name="chat_preferences_contact_allows">Ez az ismerősöd engedélyezi</string> + <string name="status_contact_has_no_e2e_encryption">az ismerősnek nincs e2e titkosítása</string> + <string name="chat_preferences_contact_allows">Ismerős engedélyezi</string> <string name="notification_preview_somebody">Ismerős elrejtve:</string> - <string name="connect_to_desktop">Kapcsolódás az asztali klienshez</string> - <string name="icon_descr_context">Kontextus ikon</string> - <string name="connect_via_link">Kapcsolódás egy linken keresztül</string> + <string name="connect_to_desktop">Kapcsolódás számítógéphez</string> + <string name="icon_descr_context">Környezeti ikon</string> + <string name="connect_via_link">Kapcsolódás egy hivatkozáson keresztül</string> <string name="receipts_section_contacts">Ismerősök</string> <string name="connection_error">Kapcsolódási hiba</string> - <string name="alert_title_contact_connection_pending">Az ismerős még nem kapcsolódott!</string> + <string name="alert_title_contact_connection_pending">Az ismerős még nem csatlakozott!</string> <string name="v5_3_discover_join_groups_descr">- kapcsolódás könyvtár szolgáltatáshoz (BÉTA)! -\n- kézbesítési igazolások (20 tagig). +\n- kézbesítési jelentések (20 tagig). \n- gyorsabb és stabilabb</string> <string name="contribute">Hozzájárulás</string> - <string name="group_member_status_intro_invitation">csatlakozás (bemutatkozás meghívás)</string> + <string name="group_member_status_intro_invitation">csatlakozás (bemutatkozás meghívó)</string> <string name="create_simplex_address">SimpleX azonosító létrehozása</string> <string name="rcv_direct_event_contact_deleted">törölt ismerős</string> - <string name="delete_member_message__question">Tag üzenetének törlése?</string> - <string name="chat_is_running">A chat szolgáltatás működik (fut)</string> - <string name="share_one_time_link">Egyszer használatos meghívó link létrehozása</string> - <string name="delete_link">Link törlése</string> + <string name="delete_member_message__question">Csoporttag üzenet törlése?</string> + <string name="chat_is_running">A csevegés fut</string> + <string name="share_one_time_link">Egyszer használatos meghívó hivatkozás létrehozása</string> + <string name="delete_link">Törlés</string> <string name="notifications_mode_periodic_desc">Új üzenetek ellenőrzése 10 percenként, legfeljebb 1 percen keresztül.</string> <string name="delete_database">Adatbázis törlése</string> <string name="create_group_button">Csoport létrehozása</string> - <string name="network_session_mode_user">Chat profil</string> + <string name="network_session_mode_user">Csevegési profil</string> <string name="create_another_profile_button">Profil létrehozása</string> <string name="connected_desktop">Csatlakoztatott számítógép</string> <string name="share_text_deleted_at">Törölve ekkor: %s</string> <string name="info_row_deleted_at">Törölve ekkor</string> <string name="v4_6_chinese_spanish_interface">Kínai és spanyol kezelőfelület.</string> <string name="alert_title_cant_invite_contacts">Ismerősök meghívása nem lehetséges!</string> - <string name="chat_is_stopped_indication">A chat szolgáltatás leállt (nem fut)</string> + <string name="chat_is_stopped_indication">A csevegés leállt</string> <string name="theme_dark">Sötét</string> <string name="create_profile">Profil létrehozása</string> <string name="rcv_group_event_group_deleted">törölt csoport</string> <string name="full_deletion">Törlés mindenkinél</string> - <string name="button_create_group_link">Link létrehozása</string> - <string name="chat_preferences">Chat beállítások</string> - <string name="chat_archive_header">Chat archívum</string> + <string name="button_create_group_link">Hivatkozás létrehozása</string> + <string name="chat_preferences">Csevegési beállítások</string> + <string name="chat_archive_header">Csevegési archívum</string> <string name="delete_profile">Profil törlése</string> - <string name="la_current_app_passcode">Jelenlegi Jelkód</string> + <string name="la_current_app_passcode">Jelenlegi jelkód</string> <string name="group_member_status_connecting">kapcsolódás</string> <string name="confirm_new_passphrase">Új jelmondat megerősítése…</string> <string name="group_connection_pending">kapcsolódás…</string> - <string name="delete_chat_profile">Chat profil törlés</string> + <string name="delete_chat_profile">Csevegési profil törlése</string> <string name="custom_time_picker_custom">egyedi</string> <string name="callstatus_connecting">hívás kapcsolódik…</string> - <string name="customize_theme_title">Színséma személyreszabása</string> + <string name="customize_theme_title">Téma személyre szabása</string> <string name="maximum_supported_file_size">Jelenleg támogatott legnagyobb fájl méret: %1$s.</string> <string name="smp_server_test_delete_file">Fájl törlése</string> <string name="in_developing_title">Hamarosan!</string> - <string name="snd_conn_event_switch_queue_phase_changing_for_member">azonosító megváltoztatása %s -ra/re…</string> - <string name="chat_database_imported">Chat adatbázis importálva</string> - <string name="chat_archive_section">CHAT ARCHÍVUM</string> - <string name="delete_messages">Üzenetek törlése?</string> + <string name="snd_conn_event_switch_queue_phase_changing_for_member">azonosító megváltoztatása erre: %s…</string> + <string name="chat_database_imported">Csevegési adatbázis importálva</string> + <string name="chat_archive_section">CSEVEGÉSI ARCHÍVUM</string> + <string name="delete_messages">Üzenetek törlése</string> <string name="clear_chat_menu_action">Kiürítés</string> <string name="icon_descr_close_button">Bezárás gomb</string> - <string name="chat_is_stopped">A chat szolgáltatás leállt (nem fut)</string> + <string name="chat_is_stopped">A csevegés leállt</string> <string name="item_info_current">(jelenlegi)</string> - <string name="v5_1_custom_themes_descr">Színsémák személyreszabása és megosztása</string> - <string name="delete_chat_profile_question">Chat profil törlése?</string> + <string name="v5_1_custom_themes_descr">Témák személyre szabása és megosztása</string> + <string name="delete_chat_profile_question">Csevegési profil törlése?</string> <string name="create_group">Titkos csoport létrehozása</string> <string name="connected_to_desktop">Csatlakozva a számítógéphez</string> - <string name="configure_ICE_servers">ICE sezrverek beállítása</string> + <string name="configure_ICE_servers">ICE kiszolgálók beállítása</string> <string name="button_delete_group">Csoport törlése</string> <string name="clear_verification">Hitelesítés törlése</string> <string name="group_member_status_creator">szerző</string> <string name="confirm_verb">Megerősítés</string> <string name="for_me_only">Törlés nálam</string> <string name="delete_messages__question">%d üzenet törlése?</string> - <string name="v5_1_custom_themes">Egyedi színsémák</string> + <string name="v5_1_custom_themes">Egyedi témák</string> <string name="group_member_status_accepted">kapcsolódás (elfogadva)</string> - <string name="smp_servers_check_address">Szerver cím ellenőrzése és újrapróbálkozás.</string> + <string name="smp_servers_check_address">Kiszolgáló címének ellenőrzése és újrapróbálkozás.</string> <string name="delete_group_question">Csoport törlése?</string> <string name="confirm_database_upgrades">Adatbázis frissítés megerősítése</string> <string name="create_your_profile">Saját profil létrehozása</string> <string name="snd_conn_event_switch_queue_phase_changing">azonosító megváltoztatása…</string> <string name="display_name_connecting">kapcsolódás…</string> <string name="icon_descr_call_connecting">Hívás kapcsolása</string> - <string name="delete_files_and_media_question">Biztosan törlöd a fájlokat és a médiatartalmakat?</string> + <string name="delete_files_and_media_question">Fájlok és a médiatartalmak törlése?</string> <string name="group_member_status_complete">befejezett</string> - <string name="chat_database_section">CHAT ADATBÁZIS</string> + <string name="chat_database_section">CSEVEGÉSI ADATBÁZIS</string> <string name="change_self_destruct_passcode">Önmegsemmisító jelkód megváltoztatása</string> <string name="smp_server_test_create_queue">Várólista létrehozása</string> <string name="colored_text">színes</string> <string name="callstate_connecting">kapcsolódás…</string> - <string name="dark_theme">Sötét színséma</string> + <string name="dark_theme">Sötét téma</string> <string name="deleted_description">törölve</string> - <string name="users_delete_question">Chat profil törlése?</string> - <string name="chat_with_developers">Chat a SimpleX fejlesztőivel</string> - <string name="delete_link_question">Link törlése?</string> + <string name="users_delete_question">Csevegési profil törlése?</string> + <string name="chat_with_developers">Csevegés a fejlesztőkkel</string> + <string name="delete_link_question">Hivatkozás törlése?</string> <string name="server_connecting">kapcsolódás</string> <string name="send_disappearing_message_custom_time">Személyreszabott idő</string> <string name="connect_via_link_incognito">Inkognítóban csatlakozva</string> - <string name="settings_section_title_chats">CHATEK</string> - <string name="v5_3_new_desktop_app_descr">Új profil létrehozása a számítógépen futó appban. 💻</string> + <string name="settings_section_title_chats">CSEVEGÉSEK</string> + <string name="v5_3_new_desktop_app_descr">Új profil létrehozása a számítógép alkalmazásban. 💻</string> <string name="group_member_status_announced">kapcsolódás (bejelentve)</string> <string name="contact_connection_pending">kapcsolódás…</string> - <string name="chat_database_deleted">Chat adatbázis törölve</string> + <string name="chat_database_deleted">Csevegési adatbázis törölve</string> <string name="group_member_status_introduced">kapcsolódás (bejelentve)</string> - <string name="create_group_link">Csoporthoz link létrehozása</string> - <string name="chat_console">Chat konzol</string> - <string name="delete_files_and_media_for_all_users">Fájlok törlése minden chat profil alatt</string> + <string name="create_group_link">Csoportos hivatkozás létrehozása</string> + <string name="chat_console">Csevegési konzol</string> + <string name="delete_files_and_media_for_all_users">Fájlok törlése minden csevegési profilból</string> <string name="smp_server_test_delete_queue">Várólista törlése</string> <string name="button_delete_contact">Ismerős törlése</string> - <string name="archive_created_on_ts">Létrehozva: %1$s</string> + <string name="archive_created_on_ts">Létrehozva ekkor: %1$s</string> <string name="rcv_conn_event_switch_queue_phase_changing">címek megváltoztatása…</string> <string name="connected_to_mobile">Csatlakoztatva a mobilhoz</string> <string name="current_passphrase">Jelenlegi jelmondat…</string> - <string name="choose_file_title">Fájl választása</string> + <string name="choose_file_title">Fájl kiválasztás</string> <string name="delete_image">Kép törlése</string> <string name="smp_server_test_create_file">Fájl létrehozása</string> <string name="create_secret_group_title">Tikos csoport létrehozása</string> <string name="clear_contacts_selection_button">Kiürítés</string> <string name="delete_contact_question">Ismerős törlése?</string> <string name="clear_verb">Kiürítés</string> - <string name="create_address_and_let_people_connect">Hozz létre egy azonosítót, hogy az ismerősök kapcsolatba léphessenek veled.</string> - <string name="v4_4_verify_connection_security_desc">Biztonsági kódok ösezhasonlítása az ismerősökkel.</string> - <string name="smp_server_test_compare_file">Fájl összehasonlítása</string> - <string name="your_chats">Chatek</string> + <string name="create_address_and_let_people_connect">Azonosító létrehozása, hogy az emberek kapcsolatba léphessenek önnel.</string> + <string name="v4_4_verify_connection_security_desc">Biztonsági kódok összehasonlítása az ismerőseiével.</string> + <string name="smp_server_test_compare_file">Fájl összehasonlítás</string> + <string name="your_chats">Csevegések</string> <string name="delete_message__question">Üzenet törlése?</string> - <string name="delete_pending_connection__question">Függő kapcsolatfelvételi kérés törlése?</string> + <string name="delete_pending_connection__question">Függő kapcsolatfelvételi kérések törlése?</string> <string name="database_encrypted">Adatbázis titkosítva!</string> - <string name="clear_chat_question">Chat kiürítése?</string> - <string name="database_downgrade">Adatbázis downgrade?</string> - <string name="clear_chat_button">Chat kiürítése</string> + <string name="clear_chat_question">Üzenetek törlése?</string> + <string name="database_downgrade">Visszatérés a korábbi adatbázis verzióra</string> + <string name="clear_chat_button">Üzenetek törlése</string> <string name="database_passphrase_will_be_updated">Adatbázis titkosítási jelmondat frissítve lesz.</string> <string name="multicast_connect_automatically">Kapcsolódás automatikusan</string> <string name="database_error">Adatbázis hiba</string> <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 ID</string> <string name="share_text_database_id">Adatbázis ID: %d</string> - <string name="developer_options">Adatbázis azonosítók és Átviteli izolációs beállítások.</string> - <string name="database_encryption_will_be_updated">Az adatbázis titkosítás jelmondata megváltoztatásra és elmentésre kerül a Keystore-ban.</string> + <string name="developer_options">Adatbázis azonosítók és átviteli izolációs beállítások.</string> + <string name="database_encryption_will_be_updated">Az adatbázis titkosítás jelmondata 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">Szerver törlése</string> - <string name="auth_device_authentication_is_disabled_turning_off">Eszközhitelesítés kikapcsolva. SimpleX zár kikapcsolása.</string> + <string name="smp_servers_delete_server">Kiszolgáló törlése</string> + <string name="auth_device_authentication_is_disabled_turning_off">Eszközhitelesítés kikapcsolva. SimpleX zárolás kikapcsolása.</string> <string name="no_call_on_lock_screen">Letiltás</string> <string name="receipts_groups_disable_for_all">Letiltás minden csoport számára</string> - <string name="receipts_groups_enable_for_all">Minden csoportnak engedélyezve</string> - <string name="feature_enabled_for_contact">engedélyezve az ismerősnek</string> + <string name="receipts_groups_enable_for_all">Engedélyezés minden csoport részére</string> + <string name="feature_enabled_for_contact">engedélyezve ismerős részére</string> <string name="disappearing_messages_are_prohibited">Az eltűnő üzenetek küldése le van tiltva ebben a csoportban.</string> <string name="delete_address">Azonosító törlése</string> <string name="ttl_week">%d hét</string> <string name="desktop_address">Számítógép azonosítója</string> <string name="ttl_s">%dmp</string> - <string name="delivery_receipts_title">Kézbesítési izagolások!</string> - <string name="auth_device_authentication_is_not_enabled_you_can_turn_on_in_settings_once_enabled">Eszközhitelesítés nincs bekapcsolva. Bekapcsolhatod a SimpleX zárat a Beállításokon keresztük, miután bekapcsoltad az eszközhitelesítést.</string> + <string name="delivery_receipts_title">Kézbesítési jelentések!</string> + <string name="auth_device_authentication_is_not_enabled_you_can_turn_on_in_settings_once_enabled">Eszközhitelesítés nem engedélyezett.A SimpleX zárolás bekapcsolható a Beállításokon keresztül, miután az eszköz hitelesítés engedélyezésre került.</string> <string name="decryption_error">Titkosítás visszafejtési hiba</string> <string name="share_text_disappears_at">Eltűnik ekkor: %s</string> <string name="icon_descr_edited">szerkesztve</string> @@ -380,48 +380,48 @@ <string name="ttl_hours">%d óra</string> <string name="ttl_months">%d hónap</string> <string name="delete_address__question">Azonosító törlése?</string> - <string name="receipts_contacts_title_disable">Letiltod az üzenet kézbesítési jelentéseket?</string> + <string name="receipts_contacts_title_disable">Üzenet kézbesítési jelentések letiltása?</string> <string name="passphrase_is_different">Az adatbázis jelmondat eltérő a Keystore-ba elmentettől.</string> <string name="direct_messages">Közvetlen üzenetek</string> <string name="icon_descr_email">E-mail</string> <string name="receipts_contacts_disable_for_all">Letiltás mindenki számára</string> <string name="settings_developer_tools">Fejlesztői eszközök</string> <string name="database_passphrase">Adatbázis jelmondat</string> - <string name="ttl_days">%d napok</string> + <string name="ttl_days">%d nap</string> <string name="icon_descr_server_status_disconnected">Szétkapcsolva</string> - <string name="encrypted_with_random_passphrase">Az adatbázis egy véletlenszerű jelmondattal van titkosítva, lecserélheted.</string> + <string name="encrypted_with_random_passphrase">Az adatbázis egy véletlenszerű jelmondattal van titkosítva, megváltoztatható.</string> <string name="ttl_h">%dó</string> <string name="ttl_w">%dhét</string> - <string name="discover_on_network">Felfedezés helyi hálózatomn keresztül</string> - <string name="v5_3_discover_join_groups">Helyi csoportok felfedezése és csatolakozás</string> + <string name="discover_on_network">Felfedezés helyi hálózaton keresztül</string> + <string name="v5_3_discover_join_groups">Helyi csoportok felfedezése és csatlakozás</string> <string name="moderated_items_description">%1$d üzenet moderálva %2$s által</string> <string name="disappearing_message">Eltűnő üzenet</string> - <string name="dont_create_address">Ne hozz létre azonosítót</string> - <string name="dont_show_again">Ne mutasd ismét</string> - <string name="auth_disable_simplex_lock">SimpleX Zár kikapcsolása</string> + <string name="dont_create_address">Ne hozzon létre azonosítót</string> + <string name="dont_show_again">Ne mutasd újra</string> + <string name="auth_disable_simplex_lock">SimpleX zárolás kikapcsolása</string> <string name="status_e2e_encrypted">e2e titkosított</string> <string name="settings_section_title_device">ESZKÖZ</string> <string name="encrypted_video_call">e2e titkosított videóhívás</string> <string name="conn_level_desc_direct">közvetlen</string> <string name="desktop_device">Számítógép</string> <string name="la_minutes">%d perc</string> - <string name="num_contacts_selected">%d ismerős(-ök) kiválasztva</string> - <string name="enable_receipts_all">Engedélyez</string> + <string name="num_contacts_selected">%d ismerős kiválasztva</string> + <string name="enable_receipts_all">Engedélyezés</string> <string name="ttl_mth">%dhónap</string> - <string name="direct_messages_are_prohibited_in_chat">A közvetlen üzenetek tagok között titltottak ebben a csoportban.</string> + <string name="direct_messages_are_prohibited_in_chat">Ebben a csoportban tiltott a tagok közötti közvetlen üzenetek küldése.</string> <string name="ttl_min">%d perc</string> - <string name="set_password_to_export_desc">Az adatbázis egy véletlenszerű jelmondattal van titkosítva. Kérlek cseréld le exportálás előtt!</string> - <string name="receipts_groups_title_disable">Letiltod az üzenet kézbesítés jelentéseket a csoportok számára?</string> + <string name="set_password_to_export_desc">Az adatbázis egy véletlenszerű jelmondattal van titkosítva. Exportálás előtti módosítás szükséges!</string> + <string name="receipts_groups_title_disable">Üzenet kézbesítés jelentéseket letiltása a csoportok számára?</string> <string name="custom_time_unit_days">nap</string> <string name="ttl_day">%d nap</string> - <string name="delete_chat_archive_question">Chat archív törlése?</string> - <string name="failed_to_create_user_duplicate_title">Duplikálódott megjelenítési név!</string> + <string name="delete_chat_archive_question">Csevegési archívum törlése?</string> + <string name="failed_to_create_user_duplicate_title">Duplikált megjelenítési név!</string> <string name="receipts_contacts_disable_keep_overrides">Letiltás (felülírások megtartásával)</string> <string name="database_upgrade">Adatbázis fejlesztése</string> - <string name="blocked_items_description">%d üzenet blokkolva</string> + <string name="blocked_items_description">%d üzenet letiltva</string> <string name="info_row_disappears_at">Eltűnik ekkor</string> <string name="ttl_weeks">%d hét</string> - <string name="feature_enabled_for_you">engedélyezve számodra</string> + <string name="feature_enabled_for_you">engedélyezve az ön számára</string> <string name="timed_messages">Eltűnő üzenetek</string> <string name="delete_group_menu_action">Törlés</string> <string name="delete_and_notify_contact">Törlés és ismerős értesítése</string> @@ -429,9 +429,9 @@ <string name="la_seconds">%d másodperc</string> <string name="delete_files_and_media_all">Minden fájl törlése</string> <string name="database_will_be_encrypted">Az adatbázis titkosításra kerül.</string> - <string name="database_passphrase_and_export">Adatbázis jelmondat és exportálás</string> + <string name="database_passphrase_and_export">Adatbázis jelmondat és -exportálás</string> <string name="database_will_be_encrypted_and_passphrase_stored">Az adatbázis titkosításra kerül és a jelmondat eltárolásra a Keystore-ban.</string> - <string name="enable_automatic_deletion_question">Engedélyezed az automatikus üzenet törlést?</string> + <string name="enable_automatic_deletion_question">Automatikus üzenet törlés engedélyezése?</string> <string name="delete_contact_menu_action">Törlés</string> <string name="mtr_error_no_down_migration">az adatbázis verzió újabb, mint az alkalmazás, de nincs lefelé migráció eddig: %s</string> <string name="simplex_link_mode_description">Leírás</string> @@ -442,7 +442,7 @@ <string name="receipts_groups_disable_keep_overrides">Letiltás (csoport felülírások megtartásával)</string> <string name="rcv_group_events_count">%d csoportesemény</string> <string name="ttl_month">%d hónap</string> - <string name="button_edit_group_profile">Csoport profil szerkesztése</string> + <string name="button_edit_group_profile">A csoport profiljának szerkesztése</string> <string name="encrypted_audio_call">e2e titkosított hanghívás</string> <string name="ttl_sec">%d mp</string> <string name="decentralized">Decentralizált</string> @@ -451,139 +451,139 @@ <string name="disable_notifications_button">Értesítések letiltása</string> <string name="devices">Eszközök</string> <string name="multicast_discoverable_via_local_network">Látható helyi hálózaton</string> - <string name="dont_enable_receipts">Ne engedélyezd</string> + <string name="dont_enable_receipts">Ne engedélyezze</string> <string name="delete_archive">Archívum törlése</string> <string name="disappearing_prohibited_in_this_chat">Az eltűnő üzenetek le vannak tiltva ebben a csevegésben.</string> <string name="chat_preferences_default">alap (%s)</string> <string name="integrity_msg_duplicate">duplikálódott üzenet</string> <string name="disconnect_desktop_question">Számítógép leválasztása?</string> - <string name="desktop_app_version_is_incompatible">A számítógépes app verzió %s inem kompatibilis ezzel az appal.</string> + <string name="desktop_app_version_is_incompatible">Számítógép kliens verziója %s nem kompatibilis ezzel az alkalmazással.</string> <string name="delivery">Kézbesítés</string> <string name="total_files_count_and_size">%d fájl %s összméretben</string> - <string name="database_passphrase_is_required">Adatbázis jelmondat szükséges chat megnyitásához.</string> + <string name="database_passphrase_is_required">Adatbázis jelmondat szükséges a csevegés megnyitásához.</string> <string name="ttl_d">%dnap</string> - <string name="receipts_contacts_enable_for_all">Mindenki számára engedélyezve</string> - <string name="delivery_receipts_are_disabled">Kézbesítési izagolások kikapcsolva!</string> - <string name="expand_verb">Lenyit</string> + <string name="receipts_contacts_enable_for_all">Engedélyezés mindenki részére</string> + <string name="delivery_receipts_are_disabled">Kézbesítési jelentések kikapcsolva!</string> + <string name="expand_verb">Kibontás</string> <string name="error_sending_message">Hiba az üzenet küldésekor</string> - <string name="la_enter_app_passcode">Add Meg A Jelkódot</string> - <string name="for_everybody">Mindenkinek</string> - <string name="encryption_renegotiation_error">Titkosítás újra egyeztetési hiba</string> + <string name="la_enter_app_passcode">Jelkód megadása</string> + <string name="for_everybody">Mindenkinél</string> + <string name="encryption_renegotiation_error">Titkosítás újraegyeztetési hiba</string> <string name="error_encrypting_database">Hiba az adatbázis titkosításakor</string> <string name="error_deleting_group">Hiba a csoport törlésekor</string> <string name="exit_without_saving">Kilépés mentés nélkül</string> <string name="v5_3_encrypt_local_files">Tárolt fájlok és médiatartalmak titkosítása</string> <string name="error_setting_address">Hiba az azonosító beállításakor</string> <string name="group_invitation_expired">A csoport meghívó lejárt</string> - <string name="error_saving_ICE_servers">Hiba az ICE szerverek elmentésekor</string> + <string name="error_saving_ICE_servers">Hiba az ICE kiszolgálók mentésekor</string> <string name="error_alert_title">Hiba</string> <string name="icon_descr_server_status_error">Hiba</string> - <string name="error_loading_xftp_servers">Hiba az XFTP szerverek betöltésekor</string> - <string name="error_loading_smp_servers">Hiba az SMP szerverek betöltésekor</string> + <string name="error_loading_xftp_servers">Hiba az XFTP kiszolgálók betöltésekor</string> + <string name="error_loading_smp_servers">Hiba az SMP kiszolgálók betöltésekor</string> <string name="error_setting_network_config">Hiba a hálózat konfigurációjának frissítésekor</string> - <string name="network_option_enable_tcp_keep_alive">TCP életbentartás engedélyezése</string> - <string name="icon_descr_flip_camera">Kamera megfordítása</string> - <string name="email_invite_body">Szia! -\nCsatlakozz hozzám SimpleX Chaten: %s</string> + <string name="network_option_enable_tcp_keep_alive">TCP életben tartásának engedélyezése</string> + <string name="icon_descr_flip_camera">Fényképezőgép megfordítása</string> + <string name="email_invite_body">Üdv! +\nCsatlakozzon hozzám SimpleX Chat-en keresztül: %s</string> <string name="display_name_cannot_contain_whitespace">A megjelenített név nem tartalmazhat szóközöket.</string> <string name="info_row_group">Csoport</string> - <string name="enter_welcome_message_optional">Írd be az üdvözlő üzenetet… (opcionális)</string> - <string name="error_exporting_chat_database">Hiba a chat adatbázis exportálásakor</string> - <string name="error_saving_file">Hiba a fájl elmentésekor</string> + <string name="enter_welcome_message_optional">Üdvözlő üzenetet megadása… (opcionális)</string> + <string name="error_exporting_chat_database">Hiba a csevegési adatbázis exportálásakor</string> + <string name="error_saving_file">Hiba a fájl mentésekor</string> <string name="encrypt_local_files">Helyi fájlok titkosítása</string> <string name="snd_conn_event_ratchet_sync_agreed">titkosítás egyeztetve %s számára</string> <string name="marked_deleted_items_description">%d üzenet megjelölve törlésre</string> <string name="conn_event_ratchet_sync_allowed">titkosítás újra egyeztetése engedélyezve</string> <string name="enable_self_destruct">Önmegsemmisítés engedélyezése</string> - <string name="v5_2_favourites_filter_descr">Olvasatlan és kedvenc chatekre szűrés.</string> - <string name="failed_to_parse_chats_title">Chatek betöltése sikertelen</string> + <string name="v5_2_favourites_filter_descr">Olvasatlan és kedvenc csevegésekre való szűrés.</string> + <string name="failed_to_parse_chats_title">A csevegések betöltése sikertelen</string> <string name="connect_plan_group_already_exists">A csoport már létezik!</string> <string name="v4_4_french_interface">Francia kezelőfelület</string> - <string name="v4_2_group_links">Csoport linkek</string> + <string name="v4_2_group_links">Csoport hivatkozások</string> <string name="v5_1_message_reactions_descr">Végre, megvannak! 🚀</string> - <string name="error_starting_chat">Hiba a chat elindításakor</string> - <string name="group_profile_is_stored_on_members_devices">A csoport profil a tagok eszközein tárolódik, nem a szervereken.</string> - <string name="enter_passphrase">Add meg a jelmondatot…</string> + <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="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 szerverek elmentésekor</string> - <string name="downgrade_and_open_chat">Downgrade és chat megnyitása</string> + <string name="error_saving_smp_servers">Hiba az SMP kiszolgálók mentésekor</string> + <string name="downgrade_and_open_chat">Visszatérés a korábbi verzióra és a csevegés megnyitása</string> <string name="icon_descr_group_inactive">A csoport inaktív</string> - <string name="v5_0_large_files_support_descr">Gyors és nincs várakozás a küldő online állapotára!</string> - <string name="error_joining_group">Hiba a csoporthoz csatlakozáskor</string> + <string name="v5_0_large_files_support_descr">Gyors és nem kell várni, amíg a feladó online lesz!</string> + <string name="error_joining_group">Hiba a csoporthoz való csatlakozáskor</string> <string name="favorite_chat">Kedvenc</string> <string name="v4_6_group_moderation">Csoport moderáció</string> <string name="choose_file">Fájl</string> - <string name="group_link">Csoport link</string> - <string name="snd_conn_event_ratchet_sync_required">titkosítás újra egyeztetés szükséges %s számára</string> + <string name="group_link">Csoport hivatkozás</string> + <string name="snd_conn_event_ratchet_sync_required">titkosítás újraegyeztetés szükséges %s számára</string> <string name="failed_to_active_user_title">Hiba a profil váltásakor!</string> <string name="settings_experimental_features">Kísérleti funkciók</string> <string name="receipts_contacts_enable_keep_overrides">Engedélyezés (felülírások megtartásával)</string> <string name="enter_correct_passphrase">Helyes jelmondat bevitele.</string> - <string name="delete_group_for_self_cannot_undo_warning">A csoport törlésre kerül számodra -ez visszafordíthatatlan!</string> + <string name="delete_group_for_self_cannot_undo_warning">A csoport törlésre kerül az ön részére - ez nem vonható vissza!</string> <string name="encrypt_database_question">Adatbázis titkosítása?</string> <string name="allow_accepting_calls_from_lock_screen">A zárolási képernyőn megjelenő hívások engedélyezése a Beállításokban.</string> <string name="conn_event_ratchet_sync_agreed">titkosítás egyeztetve</string> - <string name="receipts_contacts_title_enable">Engedélyezed az üzenet kézbesítési jelentéseket?</string> - <string name="error_saving_group_profile">Hiba a csoport profil elmentésekor</string> + <string name="receipts_contacts_title_enable">Üzenet kézbesítési jelentések engedélyezése?</string> + <string name="error_saving_group_profile">Hiba a csoport profil mentésekor</string> <string name="server_error">hiba</string> - <string name="revoke_file__message">A fájl törölve lesz a szerverekről.</string> + <string name="revoke_file__message">A fájl törölve lesz a kiszolgálóról.</string> <string name="v5_2_disappear_one_message_descr">Akkor is, ha le van tiltva a beszélgetésben.</string> <string name="v5_4_better_groups_descr">Gyorsabb csatlakozás és megbízhatóbb üzenet kézbesítés.</string> - <string name="enable_lock">Zár engedélyezése</string> + <string name="enable_lock">Zárolás engedélyezése</string> <string name="settings_section_title_help">SEGÍTSÉG</string> <string name="group_is_decentralized">Teljesen decentralizált - kizárólag tagok számára látható.</string> <string name="file_with_path">Fájl: %s</string> <string name="icon_descr_hang_up">Hívás befejezése</string> - <string name="error_deleting_link_for_group">Hiba a csoport linkjének törlésekor</string> + <string name="error_deleting_link_for_group">Hiba a csoport hivatkozásának törlésekor</string> <string name="file_saved">Fájl elmentve</string> <string name="fix_connection_question">Kapcsolat javítása?</string> <string name="files_and_media">Fájlok és médiatartalom</string> - <string name="section_title_for_console">A KONZOL SZÁMÁRA</string> - <string name="alert_text_encryption_renegotiation_failed">Titkosítás újra egyeztetés sikertelen.</string> + <string name="section_title_for_console">KONZOLHOZ</string> + <string name="alert_text_encryption_renegotiation_failed">Titkosítás újraegyeztetése sikertelen.</string> <string name="error_deleting_user">Hiba a felhasználói profil törlésekor</string> - <string name="fix_connection_not_supported_by_group_member">A javítás nem támogatott a csoporttag által</string> - <string name="enter_welcome_message">Írd be az üdvözlő üzenetet…</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ő üzenetet megadása…</string> <string name="encrypted_database">Titkosított adatbázis</string> - <string name="enter_password_to_show">Add meg a jelszót a keresőben</string> - <string name="file_will_be_received_when_contact_completes_uploading">A fájl megérkezik amint az ismerősöd befejezi a feltöltését.</string> + <string name="enter_password_to_show">Jelszó megadása a keresőben</string> + <string name="file_will_be_received_when_contact_completes_uploading">A fájl akkor érkezik meg, amikor az ismerőse befejezte annak feltöltését.</string> <string name="smp_server_test_download_file">Fájl letöltése</string> - <string name="failed_to_parse_chat_title">Chat betöltése sikertelen</string> - <string name="smp_servers_enter_manually">Szerver megadása kézileg</string> - <string name="file_will_be_received_when_contact_is_online">A fájl megérkezik amint az ismerősöd online lesz, kélrlek várj vagy nézz vissza később!</string> - <string name="error_creating_link_for_group">Hiba a csoport linkjének létrehozásakor</string> + <string name="failed_to_parse_chat_title">Csevegés betöltése sikertelen</string> + <string name="smp_servers_enter_manually">Kiszolgáló megadása kézzel</string> + <string name="file_will_be_received_when_contact_is_online">A fájl akkor érkezik meg, amikor az ismerőse online lesz, várjon, vagy ellenőrizze később!</string> + <string name="error_creating_link_for_group">Hiba a csoport hivatkozásának létrehozásakor</string> <string name="from_gallery_button">A Galériából</string> <string name="receipts_groups_enable_keep_overrides">Engedélyezés (csoport felülírások megtartásával)</string> <string name="error_deleting_contact">Hiba az ismerős törlésekor</string> - <string name="group_members_can_delete">A csoporttagok visszafordíthatatlanul törölhetnek elküldött üzeneteket. (24 óra)</string> + <string name="group_members_can_delete">Csoporttagok visszafordíthatatlanul törölhetik az elküldött üzeneteket. (24 óra)</string> <string name="error_changing_role">Hiba a szerepkör megváltoztatásakor</string> <string name="fix_connection_confirm">Javítás</string> - <string name="group_members_can_send_disappearing">A csoporttagok küldhetnek eltűnő üzeneteket.</string> + <string name="group_members_can_send_disappearing">Csoporttagok küldhetnek eltűnő üzeneteket.</string> <string name="fix_connection">Kapcsolat javítása</string> <string name="failed_to_create_user_title">Hiba a profil létrehozásakor!</string> <string name="error_adding_members">Hiba a tag(-ok) hozzáadásakor</string> <string name="icon_descr_file">Fájl</string> - <string name="group_members_can_send_files">A csoporttagok küldhetnek fájlokat és médiatartalmakat.</string> + <string name="group_members_can_send_files">Csoporttagok küldhetnek fájlokat és médiatartalmakat.</string> <string name="delete_after">Törlés miután</string> <string name="error_changing_message_deletion">Hiba a beállítás megváltoztatásakor</string> - <string name="error_updating_link_for_group">Hiba a csoport link frissítésekor</string> + <string name="error_updating_link_for_group">Hiba a csoport hivatkozás frissítésekor</string> <string name="group_member_status_group_deleted">a csoport törölve</string> <string name="snd_group_event_group_profile_updated">csoport profil frissítve</string> - <string name="error_deleting_pending_contact_connection">Hiba a függőben lévő parner kapcsolatának törlésekor</string> - <string name="error_importing_database">Hiba a chat adatbázis importálásakor</string> + <string name="error_deleting_pending_contact_connection">Hiba a függőben lévő ismerős kapcsolatának törlésekor</string> + <string name="error_importing_database">Hiba a csevegési adatbázis importálásakor</string> <string name="error_enabling_delivery_receipts">Hiba a kézbesítési jelentések engedélyezésekor!</string> - <string name="error_saving_xftp_servers">Hiba az XFTP szerverek mentésekor</string> - <string name="group_members_can_send_dms">A csoporttagok küldhetnek közvetlen üzeneteket.</string> + <string name="error_saving_xftp_servers">Hiba az XFTP kiszolgálók mentésekor</string> + <string name="group_members_can_send_dms">Csoporttagok küldhetnek közvetlen üzeneteket.</string> <string name="error_removing_member">Hiba a tag eltávolításakor</string> <string name="callstate_ended">befejeződött</string> - <string name="v4_6_group_welcome_message">A csoport üdvözlő üzenete</string> + <string name="v4_6_group_welcome_message">Csoport üdvözlő üzenete</string> <string name="group_display_name_field">Csoport neve:</string> <string name="error_sending_message_contact_invitation">Hiba a meghívó küldésekor</string> - <string name="display_name">Add meg a nevedet:</string> + <string name="display_name">Adja meg nevét:</string> <string name="error_saving_user_password">Hiba a felhasználó jelszavának mentésekor</string> - <string name="export_theme">Színséma exportálása</string> - <string name="enter_this_device_name">Add meg ennek az eszköznek a nevét…</string> + <string name="export_theme">Téma exportálása</string> + <string name="enter_this_device_name">Eszköznév megadása…</string> <string name="error">Hiba</string> <string name="alert_message_group_invitation_expired">A csoport meghívó már nem érvényes, el lett távolítva a küldője által.</string> <string name="group_full_name_field">A csoport teljes neve:</string> @@ -593,45 +593,45 @@ <string name="error_aborting_address_change">Hiba az azonosító megváltoztatásának megszakításakor</string> <string name="error_receiving_file">Hiba a fájl fogadásakor</string> <string name="conn_event_ratchet_sync_ok">titkosítás rendben</string> - <string name="error_deleting_contact_request">Hiba az ismerős kérés törlésekor</string> - <string name="receipts_groups_title_enable">Engedélyezed a csoportok számára az üzenet kézbesítési jelentéseket?</string> - <string name="fix_connection_not_supported_by_contact">A javítás nem támogatott az ismerős által</string> + <string name="error_deleting_contact_request">Hiba az ismerős kérelem törlésekor</string> + <string name="receipts_groups_title_enable">Üzenet kézbesítési jelentéseket engedélyezése csoportok számára?</string> + <string name="fix_connection_not_supported_by_contact">Ismerős általi javítás nem támogatott</string> <string name="file_not_found">Fájl nem található</string> <string name="smp_server_test_disconnect">Kapcsolat bontása</string> - <string name="group_members_can_add_message_reactions">A csoporttagok reagálhatnak emocikonokkal az üzenetekre.</string> + <string name="group_members_can_add_message_reactions">Csoporttagok üzenetreakciókat adhatnak hozzá.</string> <string name="export_database">Adatbázis exportálása</string> <string name="full_name__field">Teljes név:</string> <string name="v4_6_reduced_battery_usage">Tovább csökkentett akkumulátor használat</string> - <string name="error_stopping_chat">Hiba a chat megállításakor</string> + <string name="error_stopping_chat">Hiba a csevegés megállításakor</string> <string name="snd_conn_event_ratchet_sync_ok">titkosítás rendben %s számára</string> - <string name="delete_group_for_all_members_cannot_undo_warning">Csoport törlésre kerül minen tag számára - ez visszafordíthatatlan!</string> + <string name="delete_group_for_all_members_cannot_undo_warning">Csoport törlésre kerül minden tag számára - ez nem vonható vissza!</string> <string name="v5_2_fix_encryption_descr">Titkosítás javítása az adatmentések helyreállítása után.</string> - <string name="error_deleting_database">Hiba a chat adatbázis törlésekor</string> - <string name="simplex_link_mode_full">Teljes link</string> + <string name="error_deleting_database">Hiba a csevegési adatbázis törlésekor</string> + <string name="simplex_link_mode_full">Teljes hivatkozás</string> <string name="error_changing_address">Hiba az azonosító megváltoztatásakor</string> - <string name="group_members_can_send_voice">A csoporttagok küldhetnek hangüzeneteket.</string> + <string name="group_members_can_send_voice">Csoporttagok küldhetnek hangüzeneteket.</string> <string name="group_preferences">Csoport beállítások</string> <string name="error_with_info">Hiba: %s</string> <string name="v4_4_disappearing_messages">Eltűnő üzenetek</string> - <string name="auth_enable_simplex_lock">SimpleX Zár engedélyezése</string> - <string name="error_synchronizing_connection">Hiba a kapcsolat szinkronizációjakor</string> + <string name="auth_enable_simplex_lock">SimpleX zárolás engedélyezése</string> + <string name="error_synchronizing_connection">Hiba a kapcsolat szinkronizálása során</string> <string name="error_creating_address">Hiba az azonosító létrehozásakor</string> <string name="feature_enabled">engedélyezve</string> <string name="error_loading_details">Hiba a részletek betöltésekor</string> - <string name="error_accepting_contact_request">Hiba az ismerős kérés elfogadása</string> - <string name="snd_conn_event_ratchet_sync_allowed">%s ismerősöddel való titkosítás újra egyeztetése engedélyezve</string> - <string name="conn_event_ratchet_sync_required">titkosítás újra egyeztetés szükséges</string> - <string name="v4_6_hidden_chat_profiles">Rejtett chat profilok</string> + <string name="error_accepting_contact_request">Hiba történt a kapcsolatfelvételi kérelem elfogadásakor</string> + <string name="snd_conn_event_ratchet_sync_allowed">titkosítás újraegyeztetése engedélyezett %s számára</string> + <string name="conn_event_ratchet_sync_required">titkosítás újraegyeztetés szükséges</string> + <string name="v4_6_hidden_chat_profiles">Rejtett csevegési profilok</string> <string name="files_and_media_section">Fájlok és média</string> - <string name="image_saved">Fotók elmentve a fotóalbumba</string> + <string name="image_saved">A kép mentve a Galériába</string> <string name="hide_notification">Elrejt</string> <string name="la_immediately">Azonnal</string> <string name="files_and_media_prohibited">A fájlok- és a médiatartalom küldése le van tiltva!</string> <string name="hide_profile">Profil elrejtése</string> - <string name="how_to_use_your_servers">Hogyan használd a szervereidet</string> - <string name="v5_2_favourites_filter">Találd meg a chat üzeneteket gyorsabban</string> - <string name="import_theme">Színséma importálása</string> - <string name="import_theme_error">Hiba a színséma importálásakor</string> + <string name="how_to_use_your_servers">Kiszolgálók használata</string> + <string name="v5_2_favourites_filter">Csevegési üzenetek gyorsabb megtalálása</string> + <string name="import_theme">Téma importálása</string> + <string name="import_theme_error">Hiba a téma importálásakor</string> <string name="notification_display_mode_hidden_desc">Ismerős és üzenet elrejtése</string> <string name="incompatible_database_version">Nem kompatibilis adatbázis verzió</string> <string name="how_simplex_works">Hogyan működik a SimpleX</string> @@ -644,95 +644,95 @@ <string name="how_to">Hogyan</string> <string name="hide_verb">Elrejt</string> <string name="gallery_image_button">Kép</string> - <string name="v4_3_improved_privacy_and_security">Fejlesztett adatvédelm és biztonság</string> + <string name="v4_3_improved_privacy_and_security">Fejlesztett adatvédelem és biztonság</string> <string name="ignore">Figyelmen kívül hagyás</string> <string name="icon_descr_image_snd_complete">Kép elküldve</string> <string name="notification_preview_mode_hidden">Rejtett</string> <string name="host_verb">Házigazda</string> <string name="initial_member_role">Kezdeti szerepkör</string> - <string name="invalid_chat">Érvénytelen chat</string> - <string name="custom_time_unit_hours">órák</string> + <string name="invalid_chat">Érvénytelen csevegés</string> + <string name="custom_time_unit_hours">óra</string> <string name="incognito">Inkognitó</string> - <string name="how_to_use_simplex_chat">Hogyan használd</string> - <string name="v4_3_improved_privacy_and_security_desc">App képernyőjének elrejtése a gyakran használt appok között.</string> - <string name="v4_3_improved_server_configuration">Javított szerver konfiguráció</string> - <string name="edit_history">Történet</string> + <string name="how_to_use_simplex_chat">Használati útmutató</string> + <string name="v4_3_improved_privacy_and_security_desc">Alkalmazás képernyőjének elrejtése a gyakran használt alkalmazások között.</string> + <string name="v4_3_improved_server_configuration">Javított kiszolgáló konfiguráció</string> + <string name="edit_history">Előzmények</string> <string name="hidden_profile_password">Rejtett profil jelszó</string> <string name="import_database">Adatbázis importálása</string> <string name="import_database_confirmation">Importálás</string> <string name="icon_descr_instant_notifications">Azonnali értesítések</string> <string name="settings_section_title_incognito">Inkognitó mód</string> - <string name="import_database_question">Chat adatbázis importálása?</string> + <string name="import_database_question">Csevegési adatbázis importálása?</string> <string name="service_notifications_disabled">Azonnali értesítések kikapcsolva!</string> <string name="service_notifications">Azonnali értesítések!</string> <string name="image_descr">Kép</string> <string name="files_are_prohibited_in_group">A fájlok- és a médiatartalom küldése le van tiltva ebben a csoportban.</string> <string name="how_it_works">Hogyan működik</string> <string name="hide_dev_options">Elrejt:</string> - <string name="error_creating_member_contact">Hiba az ismerősöddel való kapcsolat létrehozásában</string> + <string name="error_creating_member_contact">Hiba az ismerőssel történő kapcsolat létrehozásában</string> <string name="enter_one_ICE_server_per_line">ICE-kiszolgálók (soronként egy)</string> - <string name="if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link"><![CDATA[Ha nem tudtok személyesen találkozni, akkor a videohívásban <b>kiszkennelheted a QR-kódot</b>, vagy a pertnered megoszthat egy meghívó linket.]]></string> - <string name="if_you_enter_passcode_data_removed">Ha az alkalmazás megnyitásakor megadod ezt a jelkódot, az alkalmazás minden adata véglegesen törlődik!</string> - <string name="if_you_cant_meet_in_person">Ha nem tudtok személyesen találkozni, mutassátok meg a QR-kódot egy videohívás során, vagy osszátok meg egymással a linket.</string> - <string name="if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel"><![CDATA[Ha nem tudtok személyesen találkozni, <b>mutassátok meg a QR-kódot a videohívásban</b>, vagy osszátok meg egymással a linket.]]></string> - <string name="network_disable_socks_info">Ha megerősíted, az üzenetküldő szerverek láthatják az IP-címedet és a szolgáltatódat, vagyis azt, hogy mely szerverekhez csatlakozol.</string> - <string name="image_will_be_received_when_contact_completes_uploading">A kép akkor érkezik meg, amikor az ismerősöd befejezi a feltöltést.</string> + <string name="if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link"><![CDATA[Ha nem tud személyesen találkozni, <b>beolvashatja a QR-kódot a videohívásban</b>, vagy az ismerőse megoszthat egy meghívó hivatkozást.]]></string> + <string name="if_you_enter_passcode_data_removed">Ha az alkalmazás megnyitásakor megadja ezt a jelkódot, az összes alkalmazásadat visszafordíthatatlanul törlődik!</string> + <string name="if_you_cant_meet_in_person">Ha nem tud személyesen találkozni, mutassa meg a QR-kódot egy videohívás során, vagy ossza meg a hivatkozást.</string> + <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 csatlakozik.</string> + <string name="image_will_be_received_when_contact_completes_uploading">A kép akkor érkezik meg, amikor az ismerőse befejezte annak feltöltését.</string> <string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 asztali 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">Ha kaptál egy SimpleX Chat meghívó linket, akkor megnyithatod azt a böngészőben:</string> - <string name="if_you_enter_self_destruct_code">Ha az alkalmazás megnyitásakor megadod az önmegsemmisítő jelkódot:</string> - <string name="found_desktop">Megtalált asztali számítógép</string> + <string name="if_you_received_simplex_invitation_link_you_can_open_in_browser">Kapott SimpleX Chat meghívó hivatkozását megnyithatja böngészőjében:</string> + <string name="if_you_enter_self_destruct_code">Ha az alkalmazás megnyitásakor az önmegsemmisítő jelkódot megadásra kerül:</string> + <string name="found_desktop">Megtalált számítógép</string> <string name="desktop_devices">Számítógépek</string> - <string name="how_to_use_markdown">Hogyan használjuk a markdown-t</string> + <string name="how_to_use_markdown">A markdown használata</string> <string name="create_chat_profile">Csevegő profil létrehozása</string> <string name="immune_to_spam_and_abuse">Spam és visszaélések elleni védelem</string> <string name="disconnect_remote_hosts">Mobilok 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="if_you_choose_to_reject_the_sender_will_not_be_notified">Ha úgy döntesz, hogy elutasítod a küldő NEM kap értesítést.</string> - <string name="icon_descr_expand_role">Szerepkör kibővítése</string> - <string name="image_will_be_received_when_contact_is_online">A kép akkor érkezik meg, amikor az ismerősöd elérhető lesz, kérlek várj vagy nézd meg később!</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ör kiválasztásának bővítése</string> + <string name="image_will_be_received_when_contact_is_online">A kép akkor érkezik meg, amikor az ismerőse elérhető lesz, várjon, vagy ellenőrizze később!</string> <string name="group_member_status_invited">meghívott</string> <string name="invalid_connection_link">Érvénytelen kapcsolati hivatkozás</string> - <string name="mute_chat">Elnémít</string> + <string name="mute_chat">Némítás</string> <string name="no_details">nincsenek részletek</string> <string name="icon_descr_call_missed">Nem fogadott hívás</string> <string name="theme_light">Világos</string> - <string name="delete_message_cannot_be_undone_warning">Az üzenet törlésre kerül - ezt nem lehet visszafordítani!</string> + <string name="delete_message_cannot_be_undone_warning">Az üzenet törlésre kerül - ez nem vonható vissza!</string> <string name="markdown_help">Markdown segítség</string> <string name="notification_preview_new_message">új üzenet</string> <string name="old_database_archive">Régi adatbázis archívum</string> <string name="network_settings_title">Hálózati beállítások</string> <string name="no_info_on_delivery">Nincs kézbesítési információ</string> <string name="moderated_description">moderált</string> - <string name="member_will_be_removed_from_group_cannot_be_undone">A tag eltávolítása a csoportból - ezt nem lehet visszafordítani!</string> - <string name="ensure_xftp_server_address_are_correct_format_and_unique">Győződj meg róla, hogy az XFTP-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nem duplikáltak.</string> - <string name="no_contacts_selected">Nincsenek ismerősök kiválasztva</string> - <string name="no_received_app_files">Nincsenek fogadott vagy küldött fájlok</string> - <string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app"><![CDATA[📱 mobil: koppints a <b>Megnyitás mobil alkalmazásban</b>, majd koppints a <b>Csatlakozás</b> gombra az alkalmazásban.]]></string> + <string name="member_will_be_removed_from_group_cannot_be_undone">A tag eltávolítása a csoportból - ez nem vonható vissza!</string> + <string name="ensure_xftp_server_address_are_correct_format_and_unique">Győződjön meg róla, hogy az XFTP-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nem duplikáltak.</string> + <string name="no_contacts_selected">Nem kerültek ismerősök kiválasztásra</string> + <string name="no_received_app_files">Nincsenek fogadott, vagy küldött fájlok</string> + <string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app"><![CDATA[📱 mobil: koppintson a <b>Megnyitás mobil alkalmazásban</b>, majd koppintson a <b>Csatlakozás</b> gombra az alkalmazásban.]]></string> <string name="markdown_in_messages">Markdown az üzenetekben</string> <string name="group_invitation_item_description">meghívás a %1$s csoportba</string> - <string name="lock_mode">Lezárás mód</string> + <string name="lock_mode">Zárolási mód</string> <string name="new_mobile_device">Új mobil eszköz</string> <string name="v5_2_fix_encryption">Kapcsolatok megtartása</string> <string name="button_add_members">Tagok meghívása</string> - <string name="message_reactions">Üzenet reakciók</string> + <string name="message_reactions">Üzenetreakciók</string> <string name="only_one_device_can_work_at_the_same_time">Egyszerre csak egy eszköz működhet</string> - <string name="connect_plan_join_your_group">Csatlakozol a csoportodhoz?</string> + <string name="connect_plan_join_your_group">Csatlakozik a csoportjához?</string> <string name="large_file">Nagy fájl!</string> <string name="info_row_local_name">Helyi név</string> - <string name="network_and_servers">Hálózat és szerverek</string> + <string name="network_and_servers">Hálózat és kiszolgálók</string> <string name="settings_notification_preview_title">Értesítés előnézet</string> - <string name="v5_4_link_mobile_desktop">Társítsd össze a mobil és az asztali alkalmazásokat! 🔗!</string> + <string name="v5_4_link_mobile_desktop">Társítsa össze a mobil és az asztali alkalmazásokat! 🔗!</string> <string name="conn_level_desc_indirect">közvetett (%1$s)</string> <string name="v4_6_reduced_battery_usage_descr">Hamarosan további fejlesztések érkeznek!</string> - <string name="message_reactions_prohibited_in_this_chat">Az üzenetreakciók ebben a csevegésben tilosak.</string> + <string name="message_reactions_prohibited_in_this_chat">Az üzenetreakciók ebben a csevegésben le vannak tiltva.</string> <string name="incorrect_code">Helytelen biztonsági kód!</string> - <string name="alert_text_fragment_encryption_out_of_sync_old_database">Ez akkor fordulhat elő, ha Te vagy az ismerősöd a régi adatbázis biztonsági másolatát használta.</string> + <string name="alert_text_fragment_encryption_out_of_sync_old_database">Ez akkor fordulhat elő, ha ön, vagy a kapcsolata régi adatbázis biztonsági mentést használt.</string> <string name="v5_3_new_desktop_app">Új asztali alkalmazás!</string> <string name="v4_6_group_moderation_descr">Most már az adminok is: \n- törölhetik a tagok üzeneteit. \n- letilthatnak tagokat (\"megfigyelő\" szerepkör)</string> <string name="rcv_group_event_member_added">%1$s meghívott</string> - <string name="message_reactions_are_prohibited">Az üzenetreakciók ebben a csoportban tilosak.</string> + <string name="message_reactions_are_prohibited">Ebben a csoportban az üzenetreakciók le vannak tiltva.</string> <string name="network_use_onion_hosts_no">Nem</string> <string name="item_info_no_text">nincs szöveg</string> <string name="member_info_section_title_member">TAG</string> @@ -742,43 +742,43 @@ <string name="invalid_contact_link">Érvénytelen hivatkozás!</string> <string name="network_use_onion_hosts_required_desc_in_alert">A csatlakozáshoz Onion host-okra lesz szükség.</string> <string name="new_in_version">Változások a %s verzióban</string> - <string name="network_use_onion_hosts_prefer_desc_in_alert">Onion host-okat használ, ha azok rendelkezésre állnak.</string> - <string name="smp_servers_invalid_address">Érvénytelen szervercím!</string> + <string name="network_use_onion_hosts_prefer_desc_in_alert">Onion host-ok használata, ha azok rendelkezésre állnak.</string> + <string name="smp_servers_invalid_address">Érvénytelen kiszolgálócím!</string> <string name="thousand_abbreviation">k</string> <string name="chat_item_ttl_none">soha</string> <string name="new_desktop"><![CDATA[<i>(új)</i>]]></string> - <string name="ensure_smp_server_address_are_correct_format_and_unique">Győződj meg róla, hogy az SMP-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva.</string> + <string name="ensure_smp_server_address_are_correct_format_and_unique">Győződjön meg arról, hogy az SMP-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nincsenek duplikálva.</string> <string name="network_use_onion_hosts_no_desc">Onion host-ok nem lesznek használva.</string> <string name="custom_time_unit_minutes">perc</string> - <string name="learn_more">Tudj meg többet</string> + <string name="learn_more">Tudjon meg többet</string> <string name="notification_new_contact_request">Új kapcsolattartási kérelem</string> <string name="joining_group">Csatlakozás a csoporthoz</string> - <string name="linked_desktop_options">Összekapcsolt asztali eszköz beállítások</string> - <string name="rcv_group_event_invited_via_your_group_link">meghívva a csoport hivatkozásodon keresztül</string> + <string name="linked_desktop_options">Összekapcsolt számítógép beállítások</string> + <string name="rcv_group_event_invited_via_your_group_link">meghívott a csoport hivatkozásán keresztül</string> <string name="rcv_group_event_member_left">elhagyta</string> - <string name="linked_desktops">Összekapcsolt asztali eszközök</string> + <string name="linked_desktops">Összekapcsolt számítógépek</string> <string name="la_no_app_password">Nincs alkalmazás jelkód</string> <string name="muted_when_inactive">Némítás, ha inaktív!</string> <string name="alert_title_group_invitation_expired">A meghívó lejárt!</string> <string name="only_stored_on_members_devices">(csak a csoporttagok tárolják)</string> - <string name="moderate_verb">Moderál</string> + <string name="moderate_verb">Moderálás</string> <string name="chat_preferences_on">be</string> <string name="v5_1_japanese_portuguese_interface">Japán és Portugál kezelőfelület</string> - <string name="message_deletion_prohibited_in_chat">Ebben a csoportban tilos az üzenetek visszafordíthatatlan törlése.</string> + <string name="message_deletion_prohibited_in_chat">Ebben a csoportban az üzenetek visszafordíthatatlan törlése le van tiltva.</string> <string name="network_use_onion_hosts_no_desc_in_alert">Onion host-ok nem lesznek használva.</string> <string name="remote_host_was_disconnected_toast"><![CDATA[A(z) <b>%s</b> eszközzel megszakadt a kapcsolat]]></string> <string name="custom_time_unit_months">hónap</string> - <string name="privacy_message_draft">Üzenet vázlat</string> + <string name="privacy_message_draft">Üzenetvázlat</string> <string name="v5_2_disappear_one_message">Egy üzenet eltüntetése</string> <string name="v4_3_irreversible_message_deletion">Visszafordíthatatlan üzenettörlés</string> <string name="videos_limit_desc">Egyszerre csak 10 videó küldhető el</string> - <string name="only_you_can_add_message_reactions">Csak te adhatsz hozzá üzenetreakciókat.</string> + <string name="only_you_can_add_message_reactions">Csak ön adhat hozzá üzenetreakciókat.</string> <string name="group_member_status_left">elhagyta</string> - <string name="message_deletion_prohibited">Ebben a csevegésben tilos az üzenetek visszafordíthatatlan törlése.</string> + <string name="message_deletion_prohibited">Ebben a csevegésben az üzenetek visszafordíthatatlan törlése le van tiltva.</string> <string name="v4_3_voice_messages_desc">Max 40 másodperc, azonnal fogadható.</string> - <string name="description_via_contact_address_link_incognito">inkognitó a kapcsolattartási címen keresztül</string> - <string name="network_use_onion_hosts_required_desc">A csatlakozáshoz Onion host-okra lesz szükség. -\nKérjük, vedd figyelembe: .onion cím nélkül nem fogsz tudni csatlakozni a szerverekhez.</string> + <string name="description_via_contact_address_link_incognito">inkognitó a ismerős azonosító hivatkozáson keresztül</string> + <string name="network_use_onion_hosts_required_desc">A kapcsolódáshoz Onion host-okra lesz szükség. +\nFigyelem: .onion cím nélkül nem fog tudni kapcsolódni a kiszolgálókhoz.</string> <string name="v4_5_italian_interface">Olasz kezelőfelület</string> <string name="system_restricted_background_in_call_title">Nincsenek háttérhívások</string> <string name="messages_section_title">Üzenetek</string> @@ -789,27 +789,27 @@ <string name="ok">Rendben</string> <string name="no_filtered_chats">Nincsenek szűrt csevegések</string> <string name="invalid_data">Érvénytelen adat</string> - <string name="ensure_ICE_server_address_are_correct_format_and_unique">Győződj meg róla, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nem duplikáltak.</string> + <string name="ensure_ICE_server_address_are_correct_format_and_unique">Győződjön meg arról, hogy a WebRTC ICE-kiszolgáló címei megfelelő formátumúak, sorszeparáltak és nem duplikáltak.</string> <string name="only_owners_can_enable_files_and_media">Csak a csoporttulajdonosok engedélyezhetik a fájlok- és a médiatartalmak küldését.</string> <string name="loading_remote_file_title">A fájl betöltése</string> <string name="no_contacts_to_add">Nincs hozzáadandó ismerős</string> - <string name="v4_5_message_draft">Üzenet vázlat</string> - <string name="display_name_invited_to_connect">meghívott, hogy csatlakozz</string> - <string name="add_contact">Egyszer használatos meghívó link</string> + <string name="v4_5_message_draft">Üzenetvázlat</string> + <string name="display_name_invited_to_connect">meghívott, hogy csatlakozzon</string> + <string name="add_contact">Egyszer használatos meghívó hivatkozás</string> <string name="notifications">Értesítések</string> <string name="images_limit_desc">Egyszerre csak 10 kép küldhető el</string> <string name="feature_offered_item_with_param">ajánlott %s: %2s</string> <string name="not_compatible">Nem kompatibilis!</string> - <string name="make_profile_private">Tedd priváttá a profilodat!</string> - <string name="message_delivery_error_title">Üzenet kézbesítési hiba</string> + <string name="make_profile_private">Tegye priváttá profilját!</string> + <string name="message_delivery_error_title">Üzenetkézbesítési hiba</string> <string name="v4_5_multiple_chat_profiles">Több csevegőprofil</string> <string name="marked_deleted_description">töröltnek jelölve</string> - <string name="user_mute">Elnémít</string> + <string name="user_mute">Elnémítás</string> <string name="link_a_mobile">Egy mobil összekapcsolá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 a te eszközeid tárolják a felhasználói profilodat, névjegyeidet, csoportjaidat és a <b>2 rétegű végponttól-végpontig titkosítással</b> küldött üzeneteidet.]]></string> - <string name="invalid_migration_confirmation">Érvénytelen migrációs visszaigazolás</string> + <string name="only_client_devices_store_contacts_groups_e2e_encrypted_messages"><![CDATA[Csak a kliensek tárolják a felhasználói profilokat, ismerősöket, csoportokat és a <b>2 rétegű végponttól-végpontig 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> <string name="invalid_QR_code">Érvénytelen QR-kód</string> @@ -817,7 +817,7 @@ <string name="live">ÉLŐ</string> <string name="mark_unread">Olvasatlannak jelölve</string> <string name="icon_descr_more_button">Több</string> - <string name="auth_log_in_using_credential">Jelentkezz be a hitelesítő adataiddal</string> + <string name="auth_log_in_using_credential">Bejelentkezés hitelesítő adatokkal</string> <string name="invalid_message_format">érvénytelen üzenet formátum</string> <string name="join_group_button">Csatlakozás</string> <string name="shutdown_alert_desc">Az értesítések az alkalmazás elindításáig nem fognak működni.</string> @@ -825,40 +825,40 @@ <string name="this_device_version"><![CDATA[<i>(ez az eszköz) v%s)</i>]]></string> <string name="feature_offered_item">ajánlott %s</string> <string name="button_leave_group">Csoport elhagyása</string> - <string name="unblock_member_desc">A %s-től érkező üzenetek megjelennek!</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 Chat-nek nincs felhasználói 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 az ismerősödnél 2 nap után, vagy a kiszolgálón 30 nap után lejártak. -\n2. Az üzenet visszafejtése sikertelen volt, mert Te vagy az ismerősöd régi adatbázis biztonsági mentést használt. +\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 csoportos hivatkozáson keresztül</string> - <string name="network_use_onion_hosts_prefer_desc">Onion host-okat használ, ha azok rendelkezésre állnak.</string> + <string name="network_use_onion_hosts_prefer_desc">Onion host-ok használata, ha azok rendelkezésre állnak.</string> <string name="invite_friends">Barátok meghívása</string> <string name="color_surface">Menük és figyelmeztetések</string> <string name="icon_descr_add_members">Tagok meghívása</string> - <string name="group_preview_join_as">Csatlakozás mint %s</string> + <string name="group_preview_join_as">csatlakozás mint %s</string> <string name="no_selected_chat">Nincs kiválasztott csevegés</string> <string name="users_delete_data_only">Csak helyi profiladatok</string> - <string name="description_via_one_time_link_incognito">inkognitó Egyszer használatos link által</string> + <string name="description_via_one_time_link_incognito">inkognitó egyszer használatos hivatkozás által</string> <string name="share_text_moderated_at">Moderálva ekkor: %s</string> - <string name="one_time_link">Egyszer használatos meghívó link</string> + <string name="one_time_link">Egyszer használatos meghívó hivatkozás</string> <string name="invalid_name">Érvénytelen név!</string> <string name="email_invite_subject">Beszélgessünk a SimpleX Chat-ben</string> <string name="info_row_moderated_at">Moderálva ekkor</string> <string name="v4_4_live_messages">Élő üzenetek</string> <string name="mark_code_verified">Ellenőrzöttként jelölve</string> - <string name="v5_2_message_delivery_receipts">Üzenet kézbesítési bizonylatok!</string> + <string name="v5_2_message_delivery_receipts">Üzenetkézbesítési bizonylatok!</string> <string name="image_descr_link_preview">hivatkozás előnézeti képe</string> - <string name="leave_group_question">Elhagyod a csoportot?</string> + <string name="leave_group_question">Csoport elhagyása?</string> <string name="chat_preferences_no">nem</string> <string name="v4_5_reduced_battery_usage_descr">Hamarosan további fejlesztések érkeznek!</string> <string name="feature_off">ki</string> - <string name="install_simplex_chat_for_terminal">Telepítsd a SimpleX Chat-et a terminálhoz</string> + <string name="install_simplex_chat_for_terminal">SimpleX Chat telepítése a terminálhoz</string> <string name="self_destruct_new_display_name">Új megjelenített név:</string> <string name="new_passphrase">Új jelmondat…</string> <string name="callstatus_missed">nem fogadott hívás</string> - <string name="database_migrations">Migrációk: %s</string> + <string name="database_migrations">Átköltöztetés: %s</string> <string name="in_reply_to">Válasz neki</string> <string name="notification_preview_mode_message">Üzenet szövege</string> <string name="notifications_will_be_hidden">Az értesítések csak az alkalmazás bezárásáig érkeznek!</string> @@ -867,57 +867,57 @@ <string name="group_member_role_member">tag</string> <string name="make_private_connection">Privát kapcsolat létrehozása</string> <string name="moderated_item_description">Moderálva %s által</string> - <string name="import_theme_error_desc">Győződj meg róla, hogy a fájl helyes YAML-szintaxist tartalmaz. Exportáld a témát, hogy legyen egy példa a téma-fájl szerkezetére.</string> + <string name="import_theme_error_desc">Győződjön meg arról, hogy a fájl helyes YAML-szintaxist tartalmaz. Exportálja a témát, hogy legyen egy példa a téma fájl szerkezetére.</string> <string name="italic_text">dőlt</string> <string name="non_content_uri_alert_title">Érvénytelen fájl elérési útvonal</string> - <string name="connect_via_group_link">Csatlakozol a csoporthoz?</string> + <string name="connect_via_group_link">Csatlakozik a csoporthoz?</string> <string name="status_no_e2e_encryption">nincs e2e titkosítás</string> <string name="new_database_archive">Új adatbázis-archívum</string> <string name="live_message">Élő üzenet!</string> <string name="invite_to_group_button">Meghívás a csoportba</string> - <string name="lock_after">Lezárás miután</string> + <string name="lock_after">Zárolás miután</string> <string name="incoming_audio_call">Bejövő hanghívás</string> <string name="keychain_error">Kulcstartó hiba</string> - <string name="join_group_question">Csatlakozol a csoporthoz?</string> - <string name="incognito_info_protects">Az inkognitó mód úgy védi a magánszférádat, hogy minden egyes kapcsolathoz új, véletlenszerű profilt használ.</string> + <string name="join_group_question">Csatlakozik a csoporthoz?</string> + <string name="incognito_info_protects">Az inkognitómód védi személyes adatait azáltal, hogy minden ismerőshöz új véletlenszerű profilt használ.</string> <string name="v5_2_more_things_descr">- stabilabb üzenetkézbesítés. -\n- egy kicsit jobb csoportok. +\n- valamivel jobb csoportok. \n- és még sok más!</string> - <string name="v5_1_message_reactions">Üzenet reakciók</string> + <string name="v5_1_message_reactions">Üzenetreakciók</string> <string name="no_connected_mobile">Nincs csatlakoztatott mobil eszköz</string> <string name="network_status">Hálózat állapota</string> - <string name="new_passcode">Új Jelkód</string> - <string name="message_delivery_error_desc">Valószínűleg ez az ismerősöd törölte a kapcsolatát veled.</string> + <string name="new_passcode">Új jelkód</string> + <string name="message_delivery_error_desc">Valószínűleg ez az ismerős törölte önnel a kapcsolatot.</string> <string name="join_group_incognito_button">Csatlakozás inkognitóban</string> - <string name="open_chat">Chat megnyitása</string> + <string name="open_chat">Csevegés megnyitása</string> <string name="callstatus_rejected">elutasított hívás</string> <string name="onboarding_notifications_mode_periodic">Rendszeres</string> <string name="feature_received_prohibited">fogadott, tiltott</string> <string name="connect_plan_repeat_connection_request">Kapcsolódási kérés megismétlése?</string> - <string name="only_you_can_delete_messages">Kizárólag csak te tudsz véglegesen törölni üzeneteket (az ismerősöd csak megjelölheti azokat törlendőként). (24 óra)</string> + <string name="only_you_can_delete_messages">Visszafordíthatatlanul csak ön törölhet üzeneteket (ismerőse csak törlésre jelölheti őket ). (24 óra)</string> <string name="role_in_group">Szerepkör</string> <string name="simplex_link_contact">SimpleX ismerős azonosítója</string> <string name="stop_file__confirm">Megállítás</string> - <string name="smp_servers_preset_server">Előre beállított szerver</string> - <string name="add_contact_or_create_group">Új chat kezdése</string> - <string name="opensource_protocol_and_code_anybody_can_run_servers">Nyílt forráskódú protokoll és program - bárki üzemeltethet szervereket.</string> + <string name="smp_servers_preset_server">Előre beállított kiszolgáló</string> + <string name="add_contact_or_create_group">Új csevegés kezdése</string> + <string name="opensource_protocol_and_code_anybody_can_run_servers">Nyílt forráskódú protokoll és forráskód – 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="secret_text">titok</string> - <string name="settings_notification_preview_mode_title">Előnézet mutatása</string> + <string name="settings_notification_preview_mode_title">Előnézet megjelenítése</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">csoport linken keresztül</string> + <string name="description_via_group_link">csoport hivatkozáson keresztül</string> <string name="network_option_ping_interval">PING időköze</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 csoport profil frissítése</string> - <string name="your_privacy">A te adatvédelmed</string> - <string name="your_simplex_contact_address">A te SimpleX azonosítód</string> - <string name="alert_text_fragment_please_report_to_developers">Kérlek jelentsd a fejlesztőknek!</string> - <string name="people_can_connect_only_via_links_you_share">Az emberek kizárólag az általad megosztott link alapján kapcsolódhatnak hozzád.</string> + <string name="save_and_update_group_profile">Mentés és csoport profil frissítése</string> + <string name="your_privacy">Adatvédelem</string> + <string name="your_simplex_contact_address">SimpleX azonosító</string> + <string name="alert_text_fragment_please_report_to_developers">Jelentse a fejlesztőknek.</string> + <string name="people_can_connect_only_via_links_you_share">Az emberek csak az ön által megosztott hivatkozáson keresztül kapcsolódhatnak.</string> <string name="prohibit_sending_disappearing">Az eltűnő üzenetek küldése le van tiltva.</string> - <string name="only_you_can_send_voice">Kizárólag te tudsz hangüzeneteket küldeni.</string> + <string name="only_you_can_send_voice">Csak ön tud hangüzeneteket küldeni.</string> <string name="update_network_settings_confirmation">Frissítés</string> <string name="icon_descr_video_snd_complete">Videó elküldve</string> <string name="update_database_passphrase">Adatbázis jelmondat megváltoztatása</string> @@ -925,72 +925,72 @@ <string name="passcode_not_changed">A jelkód nem változott!</string> <string name="refresh_qr_code">Frissítés</string> <string name="custom_time_picker_select">Választás</string> - <string name="only_you_can_make_calls">Kizárólag te tudsz hívásokat indítani.</string> + <string name="only_you_can_make_calls">Csak ön tud hívásokat indítani.</string> <string name="smp_server_test_secure_queue">Biztonságos várólista</string> - <string name="rate_the_app">Értékeld az appot</string> - <string name="share_invitation_link">Egyszer használatos link megosztása</string> + <string name="rate_the_app">Értékelje az alkalmazást</string> + <string name="share_invitation_link">Egyszer használatos hivatkozás megosztása</string> <string name="database_restore_error">Hiba az adatbázis visszaállításakor</string> <string name="group_members_2">%s és %s</string> - <string name="chat_preferences_you_allow">Te engedélyezed</string> - <string name="v4_5_reduced_battery_usage">Takarékos akkumulátor használat</string> - <string name="save_and_notify_contacts">Mentés és az ismerősök értesítése</string> + <string name="chat_preferences_you_allow">Engedélyezve</string> + <string name="v4_5_reduced_battery_usage">Csökkentett akkumulátorhasználat</string> + <string name="save_and_notify_contacts">Mentés és ismerősök értesítése</string> <string name="group_welcome_preview">Előnézet</string> - <string name="use_chat">Használd a chatet</string> + <string name="use_chat">Csevegés használata</string> <string name="share_verb">Megosztás</string> <string name="received_message">Fogadott üzenet</string> <string name="button_welcome_message">Üdvözlő üzenet</string> <string name="rcv_group_event_n_members_connected">%s, %s és %d további tag csatlakozott</string> - <string name="only_your_contact_can_make_calls">Kizárólag az ismerősöd tud hívást indítani.</string> - <string name="settings_section_title_themes">SZÍNSÉMÁK</string> + <string name="only_your_contact_can_make_calls">Csak az ismerőse tud hívást indítani.</string> + <string name="settings_section_title_themes">TÉMÁK</string> <string name="videos_limit_title">Túl sok videó!</string> - <string name="stop_chat_to_enable_database_actions">Chat szolgáltatás megállítása az adatbázis műveletek elvégzéséhez.</string> - <string name="welcome">Üdvözöllek!</string> + <string name="stop_chat_to_enable_database_actions">Csevegési szolgáltatás megállítása az adatbázis műveletek elvégzéséhez.</string> + <string name="welcome">Üdvözöljük!</string> <string name="v5_1_self_destruct_passcode">Önmegsemmisítési jelkód</string> - <string name="connect_via_link_or_qr_from_clipboard_or_in_person">(beolvasás vagy vágólapról beillesztés)</string> + <string name="connect_via_link_or_qr_from_clipboard_or_in_person">(beolvasás, vagy beillesztés a vágólapról)</string> <string name="waiting_for_video">Videóra várakozás</string> <string name="reply_verb">Válasz</string> - <string name="connect_plan_this_is_your_own_one_time_link">Ez a te egyszer használatos linked!</string> + <string name="connect_plan_this_is_your_own_one_time_link">Ez az egyszer használatos hivatkozása!</string> <string name="ntf_channel_calls">SimpleX Chat hívások</string> - <string name="connect_use_new_incognito_profile">Az új inkognító profil használata</string> - <string name="contact_developers">Kérlek frissítsd az appot és jelentsd a fejlesztőknek!</string> + <string name="connect_use_new_incognito_profile">Új inkognító profil használata</string> + <string name="contact_developers">Frissítse az alkalmazást, és lépjen kapcsolatba a fejlesztőkkel.</string> <string name="theme_simplex">SimpleX</string> - <string name="send_link_previews">Link előnézetek küldése</string> + <string name="send_link_previews">Hivatkozás előnézete</string> <string name="rcv_conn_event_verification_code_reset">biztonsági kód megváltozott</string> - <string name="notification_preview_mode_contact_desc">Kizárólag az ismerős mutatása</string> + <string name="notification_preview_mode_contact_desc">Kizárólag ismerős megjelenítése</string> <string name="icon_descr_speaker_on">Hangszóró bekapcsolva</string> - <string name="restart_the_app_to_use_imported_chat_database">Indítsd újra az appot, hogy importált chat adatbázist használj.</string> + <string name="restart_the_app_to_use_imported_chat_database">Importált csevegési adatbázis használatához indítsa újra az alkalmazást.</string> <string name="icon_descr_sent_msg_status_unauthorized_send">jogosulatlan küldés</string> - <string name="only_your_contact_can_send_voice">Kizárólag az ismerősöd tud hangüzeneteket küldeni.</string> + <string name="only_your_contact_can_send_voice">Csak az ismerőse tud hangüzeneteket küldeni.</string> <string name="icon_descr_settings">Beállítások</string> - <string name="scan_qr_to_connect_to_contact">Kapcsolat létrehozásához az ismerősöd beolvassa a QR-kódodat vagy a linket használja az appban.</string> + <string name="scan_qr_to_connect_to_contact">A csatlakozáshoz az ismerőse beolvashatja a QR-kódot, vagy használhatja az alkalmazásban található hivatkozást.</string> <string name="callstate_received_confirmation">visszaigazolás fogadása…</string> - <string name="scan_code_from_contacts_app">A biztonsági kód beolvasása az ismerősöd appjából.</string> - <string name="observer_cant_send_message_desc">Kérlek vedd fel a kapcsolatot a csoport adminnal!</string> + <string name="scan_code_from_contacts_app">Biztonsági kód beolvasása az ismerősének alkalmazásából.</string> + <string name="observer_cant_send_message_desc">Lépjen kapcsolatba a csoport adminnal.</string> <string name="icon_descr_video_on">Videó bekapcsolva</string> <string name="display_name__field">Profil neve:</string> <string name="paste_button">Beillesztés</string> - <string name="thank_you_for_installing_simplex">Köszönjük, hogy telepítetted a SimpleX Chatet!</string> - <string name="star_on_github">Csillag a GitHub-on</string> + <string name="thank_you_for_installing_simplex">Köszönjük, hogy telepítette a SimpleX Chatet!</string> + <string name="star_on_github">Csillagozás a GitHub-on</string> <string name="remove_member_confirmation">Eltávolítás</string> <string name="search_verb">Keresés</string> <string name="sync_connection_force_question">Titkosítás újraegyeztetése?</string> <string name="self_destruct_passcode_enabled">Az önmegsemmisítési jelkód engedélyezve!</string> <string name="v4_2_security_assessment">Biztonsági kiértékelés</string> <string name="color_title">Cím</string> - <string name="icon_descr_send_message">Üzenet Elküldése</string> - <string name="restore_database">Adatbázis mentés visszaállítása</string> + <string name="icon_descr_send_message">Üzenet elküldése</string> + <string name="restore_database">Adatbázismentés visszaállítása</string> <string name="revoke_file__confirm">Visszavon</string> - <string name="ask_your_contact_to_enable_voice">Kérd meg az ismerősödet, hogy engedélyezze a hangüzenetek küldését!</string> - <string name="description_you_shared_one_time_link">egyszer használatos linket osztottál meg</string> - <string name="simplex_link_mode_browser_warning">A link megnyitása böngészőben gyengítheti az adatvédelem és biztonság szintjét. A megbízhatatlan SimpleX linkek vörössel vannak kiemelve.</string> - <string name="your_ice_servers">Az ICE szervereid</string> - <string name="you_accepted_connection">A kapcsolódást elfogadtad</string> + <string name="ask_your_contact_to_enable_voice">Ismerős felkérése, hogy engedélyezze a hangüzenetek küldését.</string> + <string name="description_you_shared_one_time_link">egyszer használatos hivatkozást osztott meg</string> + <string name="simplex_link_mode_browser_warning">A hivatkozás megnyitása böngészőben gyengítheti az adatvédelem és biztonság szintjét. A megbízhatatlan SimpleX hivatkozások vörössel vannak kiemelve.</string> + <string name="your_ice_servers">ICE kiszolgálók</string> + <string name="you_accepted_connection">Kapcsolódás elfogadva</string> <string name="reject_contact_button">Elutasítás</string> <string name="notification_preview_mode_message_desc">Ismerős és üzenet mutatása</string> <string name="settings_section_title_settings">BEÁLLÍTÁSOK</string> - <string name="save_profile_password">Felhasználói fiók jelszavának elmentése</string> + <string name="save_profile_password">Felhasználói fiók jelszavának mentése</string> <string name="stop_snd_file__title">Fájl küldés megszakítása?</string> - <string name="unlink_desktop_question">Számítőgép leválasztá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">A kapcsolódáshoz közvetlen üzenet küldése</string> <string name="network_option_ping_count">PING számláló</string> @@ -1000,57 +1000,57 @@ <string name="network_use_onion_hosts_prefer">Amikor elérhető</string> <string name="voice_message_with_duration">Hangüzenet (%1$s)</string> <string name="current_version_timestamp">%s (jelenlegi)</string> - <string name="smp_servers_your_server">A te szervered</string> + <string name="smp_servers_your_server">Saját kiszolgáló</string> <string name="random_port">Véletlen</string> <string name="share_with_contacts">Megosztás ismerősökkel...</string> - <string name="sender_you_pronoun">te</string> - <string name="you_have_no_chats">Nincsenek chat üzeneteid</string> + <string name="sender_you_pronoun">ön</string> + <string name="you_have_no_chats">Nincsenek csevegési üzenetek</string> <string name="send_disappearing_message_send">Küldés</string> <string name="chat_item_ttl_seconds">%s másodperc</string> <string name="recipient_colon_delivery_status">%s: %s</string> - <string name="system_restricted_background_desc">A SimpleX nem tud a háttérben futni. Csak az alkalmazás futása közben fogod az üzeneteket megkapni.</string> + <string name="system_restricted_background_desc">A SimpleX nem tud futni a háttérben. Csak akkor fog értesítéseket kapni, ha az alkalmazás fut.</string> <string name="images_limit_title">Túl sok kép!</string> - <string name="save_archive">Archív mentése</string> + <string name="save_archive">Archívum mentése</string> <string name="group_members_n">%s, %s és %d tag</string> - <string name="auth_stop_chat">Chat szolgáltatás megállítása</string> - <string name="simplex_link_mode">SimpleX linkek</string> + <string name="auth_stop_chat">Csevegési szolgáltatás megállítása</string> + <string name="simplex_link_mode">SimpleX hivatkozások</string> <string name="v4_4_disappearing_messages_desc">Az elküldött üzenetek törlésre kerülnek a beállított idő után.</string> <string name="user_unmute">Némítás feloldása</string> <string name="share_text_sent_at">Elküldve ekkor: %s</string> <string name="connect_use_current_profile">Jelenlegi profil használata</string> <string name="this_device">Ez az eszköz</string> - <string name="share_address_with_contacts_question">Megosztod a címet az ismerőseiddel?</string> - <string name="profile_password">Profil jelszó</string> - <string name="theme">Színséma</string> + <string name="share_address_with_contacts_question">Megosztja az azonosítót az ismerősökkel?</string> + <string name="profile_password">Profiljelszó</string> + <string name="theme">Téma</string> <string name="remove_passphrase_from_settings">Jelmondat eltávolítása a beállításokból?</string> - <string name="simplex_link_group">SimpleX csoport link</string> + <string name="simplex_link_group">SimpleX csoport hivatkozás</string> <string name="icon_descr_waiting_for_image">Képre várakozás</string> <string name="self_destruct">Önmegsemmisítés</string> <string name="callstate_waiting_for_answer">várakozás válaszra…</string> <string name="text_field_set_contact_placeholder">Ismerős nevének beállítása…</string> <string name="unblock_member_button">Tag feloldása</string> <string name="scan_QR_code">QR-kód beolvasása</string> - <string name="smp_servers_test_server">Szerver tesztelése</string> - <string name="send_us_an_email">Írj nekünk e-mailben!</string> - <string name="conn_stats_section_title_servers">SZERVEREK</string> - <string name="smp_servers_test_servers">Szerverek tesztelése</string> + <string name="smp_servers_test_server">Kiszolgáló tesztelése</string> + <string name="send_us_an_email">Küldjön nekünk e-mailt</string> + <string name="conn_stats_section_title_servers">KISZOLGÁLÓK</string> + <string name="smp_servers_test_servers">Kiszolgálók tesztelése</string> <string name="la_lock_mode_passcode">Jelkód bevitele</string> <string name="la_mode_system">Rendszer</string> - <string name="submit_passcode">Elküld</string> + <string name="submit_passcode">Elküldés</string> <string name="security_code">Biztonsági kód</string> - <string name="enter_correct_current_passphrase">Kérlek írd be a helyes jelmondatot!</string> - <string name="prohibit_message_deletion">Az üzenetek véglegesen való törlése le van tiltva.</string> - <string name="prohibit_message_reactions">Az üzenetekre adott emoji reakciók le vannak tiltva.</string> + <string name="enter_correct_current_passphrase">Adja meg a helyes aktuális jelmondatát.</string> + <string name="prohibit_message_deletion">Az elküldött üzenetek visszafordíthatatlan törlése le van tiltva.</string> + <string name="prohibit_message_reactions">Üzenetreakció tiltása.</string> <string name="use_random_passphrase">Véletlenszerű jelmondat használata</string> - <string name="call_connection_peer_to_peer">ponttól-pontig</string> - <string name="run_chat_section">CHAT SZOLGÁLTATÁS INDÍTÁSA</string> - <string name="paste_the_link_you_received">Fogadott link beillesztése</string> - <string name="smp_save_servers_question">Szerverek elmentése?</string> + <string name="call_connection_peer_to_peer">egyenrangú</string> + <string name="run_chat_section">CSEVEGÉSI SZOLGÁLTATÁS INDÍTÁSA</string> + <string name="paste_the_link_you_received">Fogadott hivatkozás beillesztése</string> + <string name="smp_save_servers_question">Kiszolgálók mentése?</string> <string name="v4_2_security_assessment_desc">A SimpleX Chat biztonsága a Trail of Bits által lett auditálva.</string> <string name="rcv_group_event_updated_group_profile">módosított csoport profil</string> - <string name="settings_section_title_support">TÁMOGASD A SIMPLEX CHATET!</string> + <string name="settings_section_title_support">TÁMOGASSA A SIMPLEX CHATET!</string> <string name="simplex_service_notification_title">SimpleX Chat szolgáltatás</string> - <string name="observer_cant_send_message_title">Nem tudsz üzeneteket küldeni!</string> + <string name="observer_cant_send_message_title">Nem lehet üzeneteket küldeni!</string> <string name="is_verified">%s ellenőrzött</string> <string name="password_to_show">Jelszó mutatása</string> <string name="privacy_and_security">Adatvédelem és biztonság</string> @@ -1059,122 +1059,122 @@ <string name="sent_message">Elküldött üzenet</string> <string name="select_contacts">Ismerősök kiválasztása</string> <string name="unknown_message_format">ismeretlen üzenet formátum</string> - <string name="smp_servers_save">Szerverek elmentése</string> + <string name="smp_servers_save">Kiszolgálók mentése</string> <string name="group_welcome_title">Üdvözlő üzenet</string> <string name="network_option_seconds_label">mp</string> - <string name="profile_update_will_be_sent_to_contacts">Profil változtatása frissítésre kerül az ismerőseidnél.</string> + <string name="profile_update_will_be_sent_to_contacts">A profilfrissítés elküldésre került az ismerősök számára.</string> <string name="v5_3_simpler_incognito_mode">Egyszerűsített inkognító mód</string> - <string name="save_welcome_message_question">Üdvözlőszöbeg elmentése?</string> - <string name="restart_the_app_to_create_a_new_chat_profile">Indítsd újra az appot, hogy új felhasználói fiókot hozz létre.</string> + <string name="save_welcome_message_question">Üdvözlőszöveg mentése?</string> + <string name="restart_the_app_to_create_a_new_chat_profile">Új csevegési fiók létrehozásához indítsa újra az alkalmazást.</string> <string name="toast_permission_denied">Engedély megtagadva!</string> <string name="icon_descr_call_pending_sent">Főggőben lévő hívás</string> <string name="opening_database">Adatbázis megnyitása…</string> <string name="shutdown_alert_question">Leállítás?</string> <string name="enter_passphrase_notification_title">Jelmondat szükséges</string> <string name="onboarding_notifications_mode_title">Privát értesítések</string> - <string name="you_invited_a_contact">Meghívtál egy ismerőst</string> + <string name="you_invited_a_contact">Meghívott egy ismerőst</string> <string name="is_not_verified">%s nincs ellenőrizve</string> - <string name="contact_tap_to_connect">A csatlakozáshoz érintsd meg</string> + <string name="contact_tap_to_connect">Koppintson a csatlakozáshoz</string> <string name="this_device_name">Ennek az eszköznek a neve</string> - <string name="your_current_profile">A te jelenlegi profilod</string> + <string name="your_current_profile">Jelenlegi profil</string> <string name="smp_server_test_upload_file">Fájl feltöltése</string> <string name="prohibit_calls">Hang- és videóhívások tiltása.</string> <string name="network_use_onion_hosts_required">Megkövetelt</string> - <string name="ntf_channel_messages">SimpleX chat üzenetek</string> + <string name="ntf_channel_messages">SimpleX Chat üzenetek</string> <string name="restore_database_alert_confirm">Visszaállítás</string> <string name="setup_database_passphrase">Adatbázis jelmondat beállítása</string> <string name="color_sent_message">Elküldött üzenet</string> - <string name="notifications_mode_periodic">Rendszeresen elindul</string> - <string name="connect_plan_this_is_your_own_simplex_address">Ez a te saját SimpleX azonosítód!</string> + <string name="notifications_mode_periodic">Időszakosan indul</string> + <string name="connect_plan_this_is_your_own_simplex_address">Ez a SimpleX azonosítója!</string> <string name="group_member_status_removed">eltávolítva</string> - <string name="share_link">Link megosztása</string> - <string name="icon_descr_simplex_team">SimpleX Csapat</string> + <string name="share_link">Megosztás</string> + <string name="icon_descr_simplex_team">SimpleX csapat</string> <string name="image_descr_profile_image">profilkép</string> - <string name="your_chat_profiles">A te chat profiljaid</string> + <string name="your_chat_profiles">Csevegési profilok</string> <string name="group_member_role_owner">tulajdonos</string> <string name="la_notice_turn_on">Bekapcsolás</string> <string name="rcv_group_event_3_members_connected">%s, %s és %s csatlakozott</string> <string name="simplex_link_invitation">SimpleX egyszer használatos meghívó</string> - <string name="your_calls">A te hívásaid</string> + <string name="your_calls">Hívások</string> <string name="icon_descr_sent_msg_status_send_failed">küldés sikertelen</string> - <string name="theme_colors_section_title">SZÍNSÉMA SZÍNEK</string> + <string name="theme_colors_section_title">TÉMA SZÍNEK</string> <string name="network_options_revert">Visszaállít</string> - <string name="restore_database_alert_desc">Kérlek írd be az előző jelszót az adatbázis visszaállítása után. Ez a művelet visszafordíthatatlan.</string> + <string name="restore_database_alert_desc">Előző jelszó megadása az adatbázis biztonsági mentésének visszaállítása után. Ez a művelet nem visszavonható.</string> <string name="color_secondary">Másodlagos</string> <string name="settings_section_title_socks">SOCKS PROXY</string> <string name="save_servers_button">Mentés</string> <string name="settings_restart_app">Újraindítás</string> - <string name="smp_servers">Üzenetküldő (SMP) szerverek</string> + <string name="smp_servers">Üzenetküldő (SMP) kiszolgálók</string> <string name="video_descr">Videó</string> <string name="save_auto_accept_settings">Automatikus elfogadási beállítások mentése</string> <string name="sync_connection_force_confirm">Újraegyzetetés</string> <string name="icon_descr_waiting_for_video">Videóra várakozás</string> - <string name="your_XFTP_servers">A te Simplex fájl küldő (XFTP) szervereid</string> + <string name="your_XFTP_servers">XFTP kiszolgálók</string> <string name="icon_descr_video_off">Videó kikapcsolva</string> <string name="v4_5_private_filenames">Privát fájl nevek</string> - <string name="save_settings_question">Beállítások elmentése?</string> + <string name="save_settings_question">Beállítások mentése?</string> <string name="la_mode_passcode">Jelkód</string> <string name="unknown_error">Ismeretlen hiba</string> - <string name="smp_servers_your_server_address">A te szerver címed</string> - <string name="auth_open_chat_console">Chat konzol megnyitása</string> + <string name="smp_servers_your_server_address">Saját kiszolgáló cím</string> + <string name="auth_open_chat_console">Csevegés konzol megnyitása</string> <string name="remove_member_button">Tag eltávolítása</string> - <string name="set_database_passphrase">Beállított adatbázis jelmondat</string> + <string name="set_database_passphrase">Adatbázis jelmondat beállítása</string> <string name="view_security_code">Biztonsági kód megtekintése</string> <string name="unblock_member_question">Tag feloldása?</string> <string name="sender_may_have_deleted_the_connection_request">A küldő törölhette a kapcsolódási kérelmet.</string> <string name="wrong_passphrase">Téves adatbázis jelmondat</string> - <string name="your_SMP_servers">A te Simplex chat (SMP) szervereid</string> - <string name="send_receipts_disabled_alert_title">Üzenet kézbesítési jelentés letiltva</string> + <string name="your_SMP_servers">SMP kiszolgálók</string> + <string name="send_receipts_disabled_alert_title">Az üzenet kézbesítési jelentések le vannak tiltva</string> <string name="open_database_folder">Adatbázis mappa megnyitása</string> - <string name="description_via_one_time_link">egyszer használatos linken keresztül</string> + <string name="description_via_one_time_link">egyszer használatos hivatkozáson keresztül</string> <string name="set_group_preferences">Csoportbeállítások megadása</string> - <string name="simplex_link_connection">%1$s keresztül</string> + <string name="simplex_link_connection">ezen keresztül: %1$s</string> <string name="chat_preferences_yes">igen</string> <string name="voice_message">Hangüzenet</string> - <string name="settings_section_title_use_from_desktop">Használat asztali kliensről</string> - <string name="settings_section_title_you">TE</string> + <string name="settings_section_title_use_from_desktop">Használat számítógépről</string> + <string name="settings_section_title_you">ÖN</string> <string name="network_proxy_port">port %d</string> - <string name="to_connect_via_link_title">Kapcsolódás link által</string> + <string name="to_connect_via_link_title">Kapcsolódás hivatkozás által</string> <string name="share_address">Azonosító megosztása</string> - <string name="smp_servers_scan_qr">Szerver QR-kód beolvasá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> - <string name="stop_sharing_address">Azonosítód megosztásának szüneteltetése?</string> - <string name="auth_open_chat_profiles">Chat profilok megnyitása</string> + <string name="stop_sharing_address">Címmegosztás megállítása?</string> + <string name="auth_open_chat_profiles">Csevegés profilok megnyitása</string> <string name="connect_plan_repeat_join_request">Csatlakozási kérés megismétlése?</string> <string name="waiting_for_image">Képre várakozás</string> <string name="v4_3_voice_messages">Hangüzenetek</string> <string name="button_remove_member_question">Tag eltávolítása?</string> - <string name="verify_security_code">Biztonsági kód megtekintése</string> - <string name="rcv_group_event_user_deleted">eltávolított téged</string> - <string name="simplex_address">SimpleX cím</string> + <string name="verify_security_code">Biztonsági kód ellenőrzése</string> + <string name="rcv_group_event_user_deleted">eltávolítottak</string> + <string name="simplex_address">SimpleX azonosító</string> <string name="show_dev_options">Mutat:</string> <string name="callstate_received_answer">fogadott válasz…</string> - <string name="restore_database_alert_title">Adatbázis mentés visszaállítása?</string> + <string name="restore_database_alert_title">Adatbázismentés visszaállítása?</string> <string name="simplex_service_notification_text">Üzenetek fogadása…</string> <string name="rcv_group_event_2_members_connected">%s és %s csatlakozva</string> - <string name="you_are_observer">megfigyelő vagy</string> + <string name="you_are_observer">megfigyelő szerep</string> <string name="port_verb">Port</string> <string name="set_passcode">Jelkód beállítása</string> - <string name="whats_new">Milyen újdonságok vannak</string> + <string name="whats_new">Változáslista</string> <string name="connect_plan_open_group">Csoport megnyitása</string> <string name="info_row_sent_at">Elküldve ekkor</string> - <string name="prohibit_sending_voice">A hangüzenetek küldése le van tiltva.</string> + <string name="prohibit_sending_voice">Hangüzenetek küldésének letiltása.</string> <string name="privacy_show_last_messages">Utolsó üzenetek megjelenítése</string> - <string name="smp_servers_preset_address">Előre beállított szerver címe</string> + <string name="smp_servers_preset_address">Az előre beállított kiszolgáló címe</string> <string name="periodic_notifications_disabled">Rendszeres értesítések letiltva!</string> <string name="passcode_changed">A jelkód megváltozott!</string> - <string name="notifications_mode_off">Akkor fut, amikor az alkalmazás meg van nyitva</string> - <string name="this_QR_code_is_not_a_link">Ez a QR-kód nem egy link!</string> + <string name="notifications_mode_off">Akkor fut, ha az alkalmazás nyitva van</string> + <string name="this_QR_code_is_not_a_link">Ez a QR-kód nem egy hivatkozás!</string> <string name="waiting_for_file">Fájlra várakozás</string> <string name="core_simplexmq_version">simplexmq: v%s (%2s)</string> <string name="unlink_desktop">Szétkapcsolás</string> - <string name="incognito_random_profile">A te véletlenszerű profilod</string> + <string name="incognito_random_profile">Véletlenszerű profil</string> <string name="wrong_passphrase_title">Téves jelmondat!</string> - <string name="prohibit_message_reactions_group">Az üzenetekre adott emoji reakciók le vannak tiltva.</string> + <string name="prohibit_message_reactions_group">Az üzenetreakciók küldése le van tiltva.</string> <string name="language_system">Rendszer</string> <string name="icon_descr_received_msg_status_unread">olvasatlan</string> <string name="icon_descr_server_status_pending">Függő</string> - <string name="personal_welcome">Üdvözöllek %1$s!</string> + <string name="personal_welcome">Üdvözöljük %1$s!</string> <string name="remove_passphrase_from_keychain">Jelmondat eltávolítása a Keystrore-ból?</string> <string name="auth_unlock">Feloldás</string> <string name="prohibit_sending_disappearing_messages">Az eltűnő üzenetek küldése le van tiltva.</string> @@ -1185,18 +1185,18 @@ <string name="alert_title_skipped_messages">Kihagyott üzenetek</string> <string name="prohibit_sending_voice_messages">A hangüzenetek küldése le van tiltva.</string> <string name="set_contact_name">Ismerős nevének beállítása...</string> - <string name="only_you_can_send_disappearing">Kizárólag te tudsz eltűnő üzeneteket küldeni.</string> + <string name="only_you_can_send_disappearing">Csak ön tud eltűnő üzeneteket küldeni.</string> <string name="share_image">Kép/videó megoszása…</string> - <string name="group_info_member_you">te: %1$s</string> - <string name="your_preferences">A te beállításaid</string> + <string name="group_info_member_you">ön: %1$s</string> + <string name="your_preferences">Beállítások</string> <string name="reset_color">Színek alaphelyzetbe állítása</string> <string name="network_options_save">Mentés</string> <string name="switch_verb">Váltás</string> - <string name="paste_the_link_you_received_to_connect_with_your_contact">Illeszd be a kapott linket az ismerősödhöz való kapcsolódáshoz…</string> + <string name="paste_the_link_you_received_to_connect_with_your_contact">Kapott hivatkozás beillesztése az ismerősökhöz történő kapcsolódáshoz…</string> <string name="scan_code">Kód beolvasása</string> <string name="open_port_in_firewall_title">Port megnyitása a tűzfalon</string> <string name="callstate_starting">indítás…</string> - <string name="save_color">Szín elmentése</string> + <string name="save_color">Szín mentése</string> <string name="settings_shutdown">Leállítás</string> <string name="icon_descr_sent_msg_status_sent">elküldve</string> <string name="network_socks_toggle_use_socks_proxy">SOCKS proxy használata</string> @@ -1204,377 +1204,377 @@ <string name="privacy_redefined">Adatvédelem újraértelmezve</string> <string name="voice_message_send_text">Hangüzenet…</string> <string name="protect_app_screen">App képernyőjének védelme</string> - <string name="show_QR_code">QR-kód mutatása</string> + <string name="show_QR_code">QR-kód megjelenítése</string> <string name="icon_descr_video_call">videóhívás</string> <string name="unfavorite_chat">Nem kedvenc</string> - <string name="send_receipts">Üzenet kézbesítési jelentések</string> - <string name="icon_descr_address">SimpleX cím</string> - <string name="chat_help_tap_button">Érintsd meg a gombot</string> - <string name="save_and_notify_contact">Mentés és az ismerős értesítése</string> + <string name="send_receipts">Üzenet kézbesítési jelentések küldése</string> + <string name="icon_descr_address">SimpleX azonosító</string> + <string name="chat_help_tap_button">Koppintson a gombra</string> + <string name="save_and_notify_contact">Mentés és ismerős értesítése</string> <string name="icon_descr_call_rejected">Elutasított hívás</string> <string name="network_socks_proxy_settings">SOCKS proxy beállítások</string> <string name="image_descr_qr_code">QR-kód</string> <string name="renegotiate_encryption">Titkosítás újraegyeztetése</string> <string name="remove_passphrase">Eltávolítás</string> - <string name="network_use_onion_hosts">TOR .onion hostok használata</string> - <string name="reveal_verb">Bemutatás</string> - <string name="la_lock_mode">SimpleX Zár mód</string> + <string name="network_use_onion_hosts">Tor .onion hostok használata</string> + <string name="reveal_verb">Felfedés</string> + <string name="la_lock_mode">SimpleX zárolási mód</string> <string name="revoke_file__action">Fájl visszavonása</string> - <string name="xftp_servers">Fájl küldő/fogadó (XFTP) SimpleX szerverek</string> + <string name="xftp_servers">XFTP kiszolgálók</string> <string name="prohibit_sending_files">A fájlok- és a médiatartalom küldése le van tiltva.</string> <string name="share_file">Fájl megosztása…</string> <string name="save_verb">Mentés</string> - <string name="call_connection_via_relay">közvetítő szerveren keresztül</string> + <string name="call_connection_via_relay">átjátszón keresztül</string> <string name="stop_sharing">Megosztás leállítása</string> - <string name="snd_group_event_member_deleted">eltávolítottad %1$s</string> - <string name="save_passphrase_and_open_chat">Jelmondat elmentése és chat megnyitása</string> - <string name="save_preferences_question">Beállítások elmentése?</string> - <string name="first_platform_without_user_ids">Az első chat rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre.</string> - <string name="prohibit_direct_messages">A tagoknak való közvetlen üzenetküldés le van tiltva.</string> + <string name="snd_group_event_member_deleted">eltávolította őt: %1$s</string> + <string name="save_passphrase_and_open_chat">Jelmondat mentése és csevegés megnyitása</string> + <string name="save_preferences_question">Beállítások mentése?</string> + <string name="first_platform_without_user_ids">Az első csevegési rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre.</string> + <string name="prohibit_direct_messages">A közvetlen üzenetek küldése a tagok számára le van tiltva.</string> <string name="network_enable_socks">SOCKS proxy használata?</string> <string name="icon_descr_speaker_off">Hangszóró kikapcsolva</string> <string name="custom_time_unit_weeks">hét</string> <string name="show_call_on_lock_screen">Mutasd</string> - <string name="webrtc_ice_servers">WebRTC ICE szerverek</string> + <string name="webrtc_ice_servers">WebRTC ICE kiszolgálók</string> <string name="revoke_file__title">Fájl visszavonása?</string> <string name="button_send_direct_message">Közvetlen üzenet küldése</string> <string name="reject">Elutasítás</string> <string name="send_verb">Küldés</string> - <string name="la_lock_mode_system">Rendszer hitelesítés</string> + <string name="la_lock_mode_system">Rendszerhitelesítés</string> <string name="simplex_link_mode_browser">Böngészőn keresztül</string> - <string name="v4_6_hidden_chat_profiles_descr">A chat profiljaid védelme jelszóval!</string> - <string name="only_your_contact_can_send_disappearing">Kizárólag az ismerősöd tud eltűnő üzeneteket küldeni.</string> - <string name="your_ICE_servers">A te ICE szervereid</string> - <string name="scan_qr_code_from_desktop">QR-kód beolvasása asztali számítógépről</string> - <string name="image_descr_simplex_logo">SimpleX Logo</string> + <string name="v4_6_hidden_chat_profiles_descr">Csevegési profiljok védelme jelszóval!</string> + <string name="only_your_contact_can_send_disappearing">Csak az ismerőse tud eltűnő üzeneteket küldeni.</string> + <string name="your_ICE_servers">ICE kiszolgálók</string> + <string name="scan_qr_code_from_desktop">QR-kód beolvasása számítógépről</string> + <string name="image_descr_simplex_logo">SimpleX logó</string> <string name="unblock_member_confirmation">Feloldás</string> <string name="unmute_chat">Némítás feloldása</string> - <string name="open_simplex_chat_to_accept_call">SimpleX chat megnyitása a hívás fogadásához</string> + <string name="open_simplex_chat_to_accept_call">SimpleX Chat megnyitása a hívás fogadásához</string> <string name="stop_rcv_file__title">Fájl fogadás megszakítása?</string> - <string name="v5_4_more_things_descr">- opcionális értesítés a törölt ismerősök számára + <string name="v5_4_more_things_descr">- opcionális értesítés a törölt ismerősök számára \n- profil nevek szóközökkel \n- és továbbiak!</string> <string name="v5_0_polish_interface">Lengyel kezelőfelület</string> - <string name="smp_servers_use_server">Használd a szervert</string> + <string name="smp_servers_use_server">Kiszolgáló használata</string> <string name="share_text_received_at">Fogadva ekkor: %s</string> - <string name="la_notice_title_simplex_lock">SimpleX Zár</string> - <string name="save_and_notify_group_members">Mentés és a csoporttagok értesítése</string> - <string name="reset_verb">Alaphelyzetbe álítás</string> - <string name="only_your_contact_can_add_message_reactions">Kizárólag az ismerősöd tud emoji reakciókat adni az üzenetekre.</string> + <string name="la_notice_title_simplex_lock">SimpleX zárolás</string> + <string name="save_and_notify_group_members">Mentés és csoporttagok értesítése</string> + <string name="reset_verb">Alaphelyzetbe állítás</string> + <string name="only_your_contact_can_add_message_reactions">Csak az ismerőse tud üzenetreakciókat küldeni.</string> <string name="voice_messages">Hangüzenetek</string> - <string name="snd_group_event_user_left">te távoztál</string> + <string name="snd_group_event_user_left">elhagyta</string> <string name="icon_descr_record_voice_message">Hangüzenet rögzítése</string> - <string name="auth_simplex_lock_turned_on">SimpleX Zár bekapcsolva</string> + <string name="auth_simplex_lock_turned_on">SimpleX zárolás bekapcsolva</string> <string name="member_contact_send_direct_message">közvetlen üzenet küldése</string> - <string name="scan_from_mobile">Telefonről beolvasás</string> + <string name="scan_from_mobile">Beolvasás mobilról</string> <string name="verify_connections">Kapcsolatok ellenőrzése</string> <string name="share_message">Üzenet megosztása…</string> <string name="custom_time_unit_seconds">másodperc</string> - <string name="lock_not_enabled">SimpleX Zár nincs engedélyezve!</string> - <string name="chat_lock">SimpleX Zár</string> - <string name="your_settings">A te beállításaid</string> - <string name="your_chat_database">Chat adatbázis használata</string> + <string name="lock_not_enabled">SimpleX zárolás nincs engedélyezve!</string> + <string name="chat_lock">SimpleX zárolás</string> + <string name="your_settings">Beállítások</string> + <string name="your_chat_database">Csevegési adatbázisa</string> <string name="rcv_group_event_member_deleted">%1$s eltávolítva</string> - <string name="smp_servers_test_failed">Szerver teszt sikertelen!</string> + <string name="smp_servers_test_failed">A kiszolgáló tesztje sikertelen!</string> <string name="verify_connection">Kapcsolat ellenőrzése</string> - <string name="whats_new_read_more">Tudj meg többet</string> + <string name="whats_new_read_more">Tudjon meg többet</string> <string name="sender_cancelled_file_transfer">A küldő megszakította a fájl átvitelt.</string> - <string name="stop_chat_question">Chat szolgáltatás megállítása?</string> + <string name="stop_chat_question">Csevegési szolgáltatás megállítása?</string> <string name="info_row_received_at">Fogadva ekkor</string> <string name="accept_feature_set_1_day">Beállítva 1 nap</string> <string name="user_unhide">Felfedés</string> <string name="color_received_message">Fogadott üzenet</string> - <string name="only_your_contact_can_delete">Kizárólag az ismerősöd tud véglegesen törölni üzeneteket (te csak törlendőként tudod megjelölni azokat). (24 óra)</string> + <string name="only_your_contact_can_delete">Csak az ismerős tud visszafordíthatatlanul törölni üzeneteket (megjelölheti őket törlésre). (24 óra)</string> <string name="self_destruct_passcode_changed">Az önmegsemmisítési jelkód megváltozott!</string> - <string name="using_simplex_chat_servers">SimpleX Chat szerverek használatban.</string> - <string name="use_simplex_chat_servers__question">SimpleX Chat szerverek használata?</string> - <string name="unhide_chat_profile">Chat profil felfedése</string> + <string name="using_simplex_chat_servers">SimpleX Chat kiszolgálók használatban.</string> + <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="connect__your_profile_will_be_shared">A te %1$s SimpleX azonosítód megosztásra kerül.</string> - <string name="you_are_already_connected_to_vName_via_this_link">Már csatlakozva vagy hozzá: %1$s.</string> - <string name="your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one">A jelenlegi chat adatbázisod TÖRLÉSRE és FELCSERÉLÉSRE kerül az importált által! -\nEz a művelet nem visszavonható - a profilod, az ismerőseid, a chat üzeneteid és fájljaid mind véglegesen elvesznek!</string> - <string name="chat_with_the_founder">Ötletek és kérdések beküldése</string> - <string name="database_downgrade_warning">Figyelem: néhány adatot elveszíthetsz!</string> - <string name="tap_to_start_new_chat">Új chat kezdése</string> + <string name="connect__your_profile_will_be_shared">A %1$s SimpleX azonosítója megosztásra kerül.</string> + <string name="you_are_already_connected_to_vName_via_this_link">Már csatlakozott: %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 visszavonható - profilok, ismerősök, csevegési üzenetek és fájlok véglegesen elvesznek!</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="waiting_for_desktop">Várakozás a számítógépre…</string> - <string name="next_generation_of_private_messaging">A privát chatelés új generációja</string> + <string name="next_generation_of_private_messaging">A privát üzenetküldés következő generációja</string> <string name="update_network_settings_question">Hálózati beállítások megváltoztatása?</string> <string name="waiting_for_mobile_to_connect">Várakozás a mobiltelefon csatlakozására:</string> <string name="v4_4_verify_connection_security">Kapcsolat biztonságának ellenőrzése</string> <string name="sending_files_not_yet_supported">fájlok küldése egyelőre még nem támogatott</string> - <string name="snd_conn_event_switch_queue_phase_completed_for_member">Megváltoztattad az azonosítót erre: %s</string> + <string name="snd_conn_event_switch_queue_phase_completed_for_member">Azonosítója erre változott: %s</string> <string name="receiving_files_not_yet_supported">fájlok fogadása egyelőre még nem támogatott</string> - <string name="save_group_profile">Csoport profil elmentése</string> + <string name="save_group_profile">Csoport profil mentése</string> <string name="network_options_reset_to_defaults">Alaphelyzetbe állítás</string> - <string name="connection_error_auth_desc">Hacsak az ismerősöd nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, akkor ez hiba lehet – kérjük, jelentsd. -\nA csatlakozáshoz kérd meg az ismerősöd, hogy hozzon létre egy másik kapcsolati hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e.</string> + <string name="connection_error_auth_desc">Hacsak az ismerőse nem törölte a kapcsolatot, vagy ez a hivatkozás már használatban volt, hiba lehet – kérjük, jelentse. +\nA csatlakozáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsolati hivatkozást, és ellenőrizze, hogy a hálózati kapcsolat stabil-e.</string> <string name="video_call_no_encryption">videóhívás (nem e2e titkosított)</string> <string name="smp_servers_use_server_for_new_conn">Alkalmazás új kapcsolatokhoz</string> - <string name="periodic_notifications_desc">Az app rendszeresen lekéri az új üzeneteket - ez naponta néhány százalék akkumulátort használ. Az app nem használja a Google push értesítési rendszert — az eszközödön lévő adat nem kerül megküldésre a szervereknek.</string> - <string name="paste_desktop_address">Számítógép címének beillesztése</string> - <string name="description_via_contact_address_link">ismerős azonosítójának linkjén keresztül</string> - <string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery"><![CDATA[Az adatvédelmed megőrzése érdekkében a Google push értesítési rendszer helyett az app a <b>SimpleX háttérszolgáltatást </b> használja - ez naponta néhány százalék akkumulátort használ.]]></string> - <string name="alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry">Az ismerősöd online kell legyen ahhoz, hogy a kapcsolat létrejöjjön. -\nMegszakíthatod ezt a kapcsolatfelvételt és törölheted az ismerőst (és később ismét megpróbálhatod egy új linkkel)</string> - <string name="restore_passphrase_not_found_desc">Jelmondat nem található a Keystore-ban, kérlek írd be kézileg. Ez akkor történhet, ha helyreállítottad az appot a backup funkcióval. Ha nem így történt, az esetben lépj kapcsolatba a fejlesztőkkel!</string> - <string name="your_contacts_will_remain_connected">Az ismerőseid továbbra is megmaradnak</string> - <string name="error_xftp_test_server_auth">A szervernek engedélyre van szüksége a várólisták létrehozásához, ellenőrizd a jelszavadat</string> - <string name="database_initialization_error_desc">Az adatbázis nem működik megfelelően. Koppints a további információkért</string> + <string name="periodic_notifications_desc">Az új üzenetek rendszeresen letöltésre kerülnek az alkalmazás által – naponta néhány százalékot használ az akkumulátorból. Az alkalmazás nem használ push értesítéseket – az eszközről származó adatok nem kerülnek elküldésre a kiszolgálóknak.</string> + <string name="paste_desktop_address">Számítógép azonosítójának beillesztése</string> + <string name="description_via_contact_address_link">ismerős azonosítójának hivatkozásán keresztül</string> + <string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery"><![CDATA[Az adatvédelem megőrzése érdekkében a push értesítési rendszer helyett az alkalmazás a <b>SimpleX háttérszolgáltatást </b> használja - az akkumulátor néhány százalékát használja naponta.]]></string> + <string name="alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry">Az ismerősnek online kell lennie ahhoz, hogy a kapcsolat létrejöjjön. +\nMegszakíthatja ezt a kapcsolatfelvételt és törölheti az ismerőst (ezt később ismét megpróbálhatja egy új hivatkozással)</string> + <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ági menté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 csatlakoztatva 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="stop_snd_file__message">A fájl küldése leállt.</string> - <string name="trying_to_connect_to_server_to_receive_messages">Csatlakozási kísérlet a kapcsolat üzeneteinek fogadására használt kiszolgálóhoz ettől az ismerősödtől.</string> - <string name="la_could_not_be_verified">Nem sikerült ellenőrizni téged; kérjük, próbáld meg újra.</string> + <string name="trying_to_connect_to_server_to_receive_messages">Csatlakozási kísérlet a kapcsolat üzeneteinek fogadására használt kiszolgálóhoz ettől az ismerőstől.</string> + <string name="la_could_not_be_verified">Nem lehetett ellenőrizni; próbálja meg újra.</string> <string name="moderate_message_will_be_marked_warning">Az üzenet minden tag számára moderáltként lesz megjelölve.</string> - <string name="enter_passphrase_notification_desc">Ha szeretnél értesítéseket kapni, kérjük, add meg az adatbázis jelszavát.</string> + <string name="enter_passphrase_notification_desc">Értesítések fogadásához adja meg az adatbázis jelmondatát</string> <string name="error_smp_test_failed_at_step">A teszt a(z) %s lépésnél sikertelen volt.</string> - <string name="auth_you_will_be_required_to_authenticate_when_you_start_or_resume">Az alkalmazás indításakor, vagy 30 másodpercnyi háttérben töltött idő után az alkalmazáshoz visszatérve hitelesítened kell magad.</string> + <string name="auth_you_will_be_required_to_authenticate_when_you_start_or_resume">Az alkalmazás indításakor, vagy 30 másodpercnyi háttérben töltött idő után az alkalmazáshoz visszatérve hitelesítés szükséges.</string> <string name="moderate_message_will_be_deleted_warning">Az üzenet minden tag számára törlésre kerül.</string> - <string name="video_decoding_exception_desc">A videó nem dekódolható. Kérjük, próbálj meg egy másik videót, vagy lépj kapcsolatba a fejlesztőkkel.</string> + <string name="video_decoding_exception_desc">A videó nem dekódolható. Próbálja ki egy másik videóval, vagy lépjen kapcsolatba a fejlesztőkkel.</string> <string name="this_text_is_available_in_settings">Ez a szöveg a beállítások között érhető el</string> - <string name="profile_will_be_sent_to_contact_sending_link">A profilodat elküldjük annak az ismerősödnek, akitől ezt a linket kaptad.</string> + <string name="profile_will_be_sent_to_contact_sending_link">Profilja elküldésre kerül ismerőse számára, akitől ezt a hivatkozást kapta.</string> <string name="system_restricted_background_in_call_desc">Az alkalmazás 1 perc után bezárható a háttérben.</string> - <string name="group_preview_you_are_invited">meg lettél hívva a csoportba</string> - <string name="turn_off_battery_optimization"><![CDATA[Használatához kérjük, <b>engedélyezd a SimpleX háttérben történő futtatását</b> a következő párbeszédpanelen. Ellenkező esetben az értesítések le lesznek tiltva.]]></string> - <string name="error_smp_test_server_auth">A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrízd a jelszavadat</string> - <string name="you_will_join_group">Csatlakozni fogsz a csoport összes tagjához.</string> + <string name="group_preview_you_are_invited">meghívást kapott a csoportba</string> + <string name="turn_off_battery_optimization"><![CDATA[Használatához <b>engedélyezze a SimpleX háttérben történő futását</b> a következő párbeszédpanelen. Ellenkező esetben az értesítések letiltásra kerülnek.]]></string> + <string name="error_smp_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="you_will_join_group">Csatlakozni fog a csoport összes tagjához.</string> <string name="error_smp_test_certificate">Lehetséges, hogy a kiszolgáló címében szereplő tanúsítvány-ujjlenyomat helytelen</string> - <string name="la_notice_to_protect_your_information_turn_on_simplex_lock_you_will_be_prompted_to_complete_authentication_before_this_feature_is_enabled">Az adatok védelme érdekében kapcsold be a SimpleX Lock funkciót. -\nA funkció engedélyezése előtt a rendszer felszólít téged a hitelesítés befejezésére.</string> - <string name="video_will_be_received_when_contact_is_online">A videó akkor érkezik, amikor az ismerősöd elérhető, kérlek várj vagy nézd meg később!</string> - <string name="network_error_desc">Kérjük, ellenőrizd hálózati kapcsolatodat a(z) %1$s segítségével, és próbáld meg újra.</string> - <string name="you_can_turn_on_lock">A SimpleX Lock-ot a Beállításokon keresztül kapcsolhatod be.</string> + <string name="la_notice_to_protect_your_information_turn_on_simplex_lock_you_will_be_prompted_to_complete_authentication_before_this_feature_is_enabled">Az adatavédelem érdekében kapcsolja be a SimpleX zárolás funkciót. +\nA funkció engedélyezése előtt a rendszer felszólítja a hitelesítés befejezésére.</string> + <string name="video_will_be_received_when_contact_is_online">A videó akkor érkezik meg, amikor az ismerőse elérhető, várjon, vagy ellenőrizze később!</string> + <string name="network_error_desc">Hálózati kapcsolat ellenőrzése a következővel: %1$s, és próbálja újra.</string> + <string name="you_can_turn_on_lock">A SimpleX zárolás a Beállításokon keresztül kapcsolható be.</string> <string name="app_was_crashed">Az alkalmazás összeomlott</string> - <string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Kérjük, ellenőrizd, hogy a megfelelő linket használtad-e, vagy kérd meg az ismerősödet, hogy küldjön egy másikat.</string> - <string name="image_decoding_exception_desc">A kép nem dekódolható. Kérjük, próbálj meg egy másik képet, vagy lépj kapcsolatba a fejlesztőkkel.</string> - <string name="non_content_uri_alert_text">Érvénytelen fájl elérési útvonalat osztottál meg. Jelentsd a problémát az alkalmazás fejlesztőinek.</string> - <string name="failed_to_create_user_duplicate_desc">Már van egy chat-profilod ugyanezzel a megjelenített névvel. Kérjük, válassz másik nevet.</string> - <string name="trying_to_connect_to_server_to_receive_messages_with_error">Csatlakozási kísérlet a kapcsolat üzeneteinek fogadására használt szerverhez ettől az ismerősödtől (hiba: %1$s).</string> + <string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Ellenőrizze, hogy a megfelelő hivatkozást használta-e, vagy kérje meg ismerősét, hogy küldjön egy másikat.</string> + <string name="image_decoding_exception_desc">A kép nem dekódolható. Kérjük, próbálja ki egy másik képpel, vagy lépjen kapcsolatba a fejlesztőkkel.</string> + <string name="non_content_uri_alert_text">Érvénytelen fájl elérési útvonalat osztott meg. Jelentse a problémát az alkalmazás fejlesztőinek.</string> + <string name="failed_to_create_user_duplicate_desc">Már van egy csevegési profil ugyanezzel a megjelenített névvel. Válasszon egy másik nevet.</string> + <string name="trying_to_connect_to_server_to_receive_messages_with_error">Csatlakozási kísérlet a kapcsolat üzeneteinek fogadására használt kiszolgálóhoz ettől az ismerőstől (hiba: %1$s).</string> <string name="stop_rcv_file__message">A fájl fogadása leállt.</string> - <string name="la_please_remember_to_store_password">Kérjük, jegyezd meg vagy tárold biztonságosan - az elveszett jelszót nem lehet visszaállítani!</string> - <string name="video_will_be_received_when_contact_completes_uploading">A videó akkor érkezik meg, amikor az ismerősöd befejezi a feltöltést.</string> - <string name="description_you_shared_one_time_link_incognito">egyszeri linket osztottál meg inkognitóban</string> - <string name="connected_to_server_to_receive_messages_from_contact">Csatlakozol ahhoz a kiszolgálóhoz, amely az adott ismerősödtől érkező üzenetek fogadására szolgál.</string> - <string name="you_can_enable_delivery_receipts_later">Később engedélyezheted a Beállításokban</string> - <string name="you_will_be_connected_when_group_host_device_is_online">Csatlakozni fogsz a csoporthoz amikor a csoport tulajdonosának az eszköze online lesz. Kérlek várj vagy nézz vissza később!</string> + <string name="la_please_remember_to_store_password">Ne felejtse el, vagy tárolja biztonságosan – az elveszett jelszót nem lehet visszaállítani!</string> + <string name="video_will_be_received_when_contact_completes_uploading">A videó akkor érkezik meg, amikor az ismerőse befejezte annak feltöltését.</string> + <string name="description_you_shared_one_time_link_incognito">egyszer használatos hivatkozást osztott meg inkognitóban</string> + <string name="connected_to_server_to_receive_messages_from_contact">Kiszolgálóhoz történő csatlakozás, mely az adott ismerőstől érkező üzenetek fogadására szolgál.</string> + <string name="you_can_enable_delivery_receipts_later">Később engedélyezheti a Beállításokban</string> + <string name="you_will_be_connected_when_group_host_device_is_online">Akkor tud csatlakozni a csoporthoz, amikor a csoport tulajdonosának eszköze online lesz, várjon, vagy ellenőrizze később!</string> <string name="mtr_error_different">eltérő migráció az appban/adatbázisban: %s / %s</string> - <string name="connect_plan_you_are_already_connecting_to_vName"><![CDATA[Már kapcsolatban vagy vele: <b>%1$s</b>.]]></string> + <string name="connect_plan_you_are_already_connecting_to_vName"><![CDATA[Már csatlakozik a következőhöz: <b>%1$s</b>.]]></string> <string name="unhide_profile">Profil felfedése</string> - <string name="this_link_is_not_a_valid_connection_link">Ez a link nem érvényes meghívó link!</string> - <string name="to_verify_compare">A felek közötti titkosítás (e2e) ellenőrzéséhez hasonlítsd össze (vagy olvasd be) a kódot az ismerősöddel együtt az eszközeiteken!</string> - <string name="you_must_use_the_most_recent_version_of_database">A csevegési adatbázis legfrissebb verzióját CSAK egy eszközön kell használnod, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogod megkapni valamennyi kapcsolatodtól.</string> - <string name="messages_section_description">Ez a beállítás a jelenlegi chat profilodban lévő üzenetekre érvényes</string> - <string name="you_are_invited_to_group_join_to_connect_with_group_members">Meg vagy hívva a csoportba. Csatlakozz és lépj kapcsolatba a csoporttagokkal!</string> - <string name="alert_message_no_group">Ez a csoport többé nem létezik.</string> - <string name="connect_plan_you_are_already_joining_the_group_via_this_link">A csatlakozásod már folyamatban van a csoporthoz ezzel a linkkel!</string> - <string name="you_are_invited_to_group">Meg vagy hívva a csoportba</string> - <string name="contact_sent_large_file">Az ismerősöd a jelenleg megengedett maximális méretű (%1$s) fájlnál nagyobbat küldött.</string> - <string name="we_do_not_store_contacts_or_messages_on_servers">Nem tároljuk egyetlen üzenetedet illetve ismerőseidet (kézbesítás után) a SimpleX szervereken.</string> + <string name="this_link_is_not_a_valid_connection_link">Ez a hivatkozás nem érvényes kapcsolati hivatkozás!</string> + <string name="to_verify_compare">A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy szkennelje be) az ismerőse eszközén lévő kódot.</string> + <string name="you_must_use_the_most_recent_version_of_database">A csevegési adatbázis legfrissebb verzióját CSAK egy eszközön kell használnia, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogja megkapni valamennyi ismerősétől.</string> + <string name="messages_section_description">Ez a beállítás a jelenlegi csevegési profilban lévő üzenetekre érvényes</string> + <string name="you_are_invited_to_group_join_to_connect_with_group_members">Meghívást kapott a csoportba. Csatlakozzon, hogy kapcsolatba léphessen a csoport tagjaival.</string> + <string name="alert_message_no_group">Ez a csoport már nem létezik.</string> + <string name="connect_plan_you_are_already_joining_the_group_via_this_link">Ezen a hivatkozáson keresztül már csatlakozik a csoporthoz.</string> + <string name="you_are_invited_to_group">Meghívást kapott a csoportba</string> + <string name="contact_sent_large_file">Ismerőse a jelenleg megengedett maximális méretű (%1$s) fájlnál nagyobbat küldött.</string> + <string name="we_do_not_store_contacts_or_messages_on_servers">Az ismerősei és az üzenetek (kézbesítés után) nem kerülnek tárolásra a SimpleX kiszolgálókon.</string> <string name="you_can_use_markdown_to_format_messages__prompt">Üzenetek formázása a szövegbe szúrt speciális karakterekkel:</string> - <string name="you_can_also_connect_by_clicking_the_link"><![CDATA[A linkre kattintással is kapcsolódhatsz. Ha megnyílik böngészőben, kattints a<b>Mobil appban megnyitás</b> gombra.]]></string> - <string name="your_chat_profile_will_be_sent_to_your_contact">A chat profilod megküldésre kerül -\naz ismerősöd számára</string> - <string name="invite_prohibited_description">Egy olyan ismerőst próbálsz meghívni, akivel inkognító profilt osztottál meg abban a csoportban, amelyben a saját fő profilodat használod</string> - <string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[Már folyamatban van a csatlakozásod a(z) <b>%1$s</b> csoporthoz.]]></string> - <string name="onboarding_notifications_mode_off">Amikor az app fut</string> - <string name="alert_title_cant_invite_contacts_descr">Inkognító profilt használsz ehhez a csoporthoz - a fő profilod megosztásának elkerülése érdekében meghívók küldése tiltott</string> + <string name="you_can_also_connect_by_clicking_the_link"><![CDATA[A hivatkozásra kattintva is kapcsolódhat. Ha megnyílik böngészőben, kattintson a<b>Megnyitás alkalmazásban</b> gombra.]]></string> + <string name="your_chat_profile_will_be_sent_to_your_contact">Csevegési profilja elküldésre kerül +\naz ismerőse számára</string> + <string name="invite_prohibited_description">Egy olyan ismerősét próbálja meghívni, akivel inkognitó profilt osztott meg abban a csoportban, amelyben a saját fő profilja van használatban</string> + <string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[Csatlakozás folyamatban van a(z) <b>%1$s</b> csoporthoz.]]></string> + <string name="onboarding_notifications_mode_off">Amikor az alkalmazás fut</string> + <string name="alert_title_cant_invite_contacts_descr">Inkognító profilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében meghívók küldése tiltott</string> <string name="v4_5_transport_isolation">Kapcsolat izolációs mód</string> - <string name="you_will_be_connected_when_your_connection_request_is_accepted">Csatlakoztok amikor a meghívód elfogadásra kerül. Kérlek várj vagy nézz vissza később!</string> + <string name="you_will_be_connected_when_your_connection_request_is_accepted">Akkor lesz csatlakoztatva, ha a csatlakozási kérelme elfogadásra került, 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 kérlek válaszd az <b>App akkumulátor használat</b> / <b>Korlátlan</b> módot az app beállításaiban.]]></string> - <string name="v5_4_link_mobile_desktop_descr">Biztonságos kvantum ellenálló protokoll által.</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> + <string name="v5_4_link_mobile_desktop_descr">Biztonságos kvantumrezisztens protokollon keresztül.</string> <string name="v5_1_better_messages_descr">- hangüzenetek 5 percig. \n- egyedi eltűnési időhatár \n- előzmény szerkesztése</string> - <string name="open_on_mobile_and_scan_qr_code"><![CDATA[Kattints a <i>Használd a számítógépről</i> gombra a mobil appban és olvasd be a QR-kódot!]]></string> + <string name="open_on_mobile_and_scan_qr_code"><![CDATA[Megnyitás a <i>Használat számítógépről</i> gombra a mobil appban és olvassa be a QR-kódot!]]></string> <string name="sender_at_ts">%s at %s</string> - <string name="you_will_be_connected_when_your_contacts_device_is_online">Csatlakoztok amikor az ismerősöd eszköze online lesz. Kérlek várj vagy nézz vissza később!</string> + <string name="you_will_be_connected_when_your_contacts_device_is_online">Akkor csatlakozik, amikor az ismerősének az eszköze online lesz, várjon, vagy ellenőrizze később!</string> <string name="v5_4_block_group_members_descr">Kéretlen üzenetek elrejtése.</string> - <string name="disable_onion_hosts_when_not_supported"><![CDATA[Állítsd a <i>Használd az .onion hostokat</i> NEM-re ha a SOCKS proxy nem támogatja.]]></string> - <string name="you_can_share_your_address">Megoszthatod a SimpleX azonosítódat linkben vagy QR-kódban - bárki kapcsolatfelvételt kezdeményezhet veled.</string> - <string name="you_can_create_it_later">Később létrehozhatod</string> - <string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">A profilod a te eszközödön van tárolva és csak az ismerőseiddel kerül megosztásra! A SimpleX chat szerverek nem láthatják a profilodat!</string> - <string name="snd_group_event_changed_member_role">%s szerepkörét megváltoztattad erre: %s</string> - <string name="you_rejected_group_invitation">Elutasítottad a meghívót a csoportba</string> - <string name="to_protect_privacy_simplex_has_ids_for_queues">Az adatvédelem érdekében, a más chat platformokon megszokott felhasználói azonosítók helyett, a SimpleX üzenetsorokhoz rendel azonosítókat, minden egyes ismerősödhöz egy különbözőt.</string> - <string name="to_share_with_your_contact">(megosztás az ismerősöddel)</string> - <string name="you_sent_group_invitation">Csoport meghívót küldtél</string> + <string name="disable_onion_hosts_when_not_supported"><![CDATA[Állítsa a <i>Használja az .onion hostokat</i> NEM értékre, ha a SOCKS proxy nem támogatja őket.]]></string> + <string name="you_can_share_your_address">Megoszthatja azonosítóját hivatkozásként vagy QR-kódként – így bárki csatlakozhat önhöz.</string> + <string name="you_can_create_it_later">Létrehozás később</string> + <string name="your_profile_is_stored_on_device_and_shared_only_with_contacts_simplex_cannot_see_it">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">%s szerepkörét megváltoztatta erre: %s</string> + <string name="you_rejected_group_invitation">Csoport meghí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 üzenetsorokhoz rendel azonosítókat, minden egyes ismerőshöz egy különbözőt.</string> + <string name="to_share_with_your_contact">(megosztás egy ismerőssel)</string> + <string name="you_sent_group_invitation">Csoport meghí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="you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved">Ettől a csoporttól nem fogsz értesítéseket kapni. A chat előzmény megmarad.</string> - <string name="database_is_not_encrypted">A chat adatbázisod nem titkosított - állíts be jelmondatot a megvédéséhez!</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> - <string name="you_will_still_receive_calls_and_ntfs">Továbbra is kapsz hívásokat és értesítéseket az elnémított profilokból amikor azok aktívak.</string> - <string name="group_main_profile_sent">A saját fő chat profilod megküldésre kerül a csoporttagok számára.</string> - <string name="you_can_enable_delivery_receipts_later_alert">Később engedélyezheted az alkalmazás Adatvédelmi és Biztonsági beállításaiban.</string> - <string name="to_reveal_profile_enter_password">A rejtett profilod felfedéséhez gépeld be a jelszót a kereső mezőbe a Chat profiljaid oldalon!</string> - <string name="upgrade_and_open_chat">A chat frissítése és megnyitása</string> - <string name="you_need_to_allow_to_send_voice">Ahhoz, hogy hangüzeneteket küldhess, engedélyezned kell az ismerőseidnek is azok küldését.</string> - <string name="you_control_servers_to_receive_your_contacts_to_send"><![CDATA[Te kontrollálod, hogy melyik szerveren(-ken) keresztül <b>fogadod</b>az üzeneteket, míg az ismerőseid a szervereket amelyeken át üzensz nekik.]]></string> - <string name="connect_plan_you_are_already_in_group_vName"><![CDATA[Már a(z) <b>%1$s</b> csoportban vagy.]]></string> - <string name="snd_conn_event_switch_queue_phase_completed">megváltoztattad az azonosítót</string> - <string name="v4_3_irreversible_message_deletion_desc">Az ismerőseid engedélyezhetik a teljes üzenet törlést.</string> - <string name="you_have_to_enter_passphrase_every_time">Be kell írd a jelmondatodat a SimpleX app minden indulásakor - nem az eszközön lesz tárolva.</string> - <string name="open_port_in_firewall_desc">Ahhoz, hogy engedélyezd a mobil app csatlakozását a számítógépedhez, nyisd meg ezt a portot a tűzfaladon, ha az engedélyezve van</string> - <string name="your_profile_is_stored_on_your_device">A te profilod, ismerőseid és az elküldött üzeneteid a te eszközödön vannak tárolva (a SimpleX chat szerverekről kézbesítés illetve a TTL időkorlát után törlődnek).</string> - <string name="system_restricted_background_warn"><![CDATA[Az értesítések engedélyezéséhez kérlek válaszd az <b>App akkumulátor használat</b> / <b>Korlátlan</b> módot az app beállításaiban.]]></string> - <string name="this_string_is_not_a_connection_link">Ez a karakterlánc nem meghívó link!</string> - <string name="to_start_a_new_chat_help_header">Új chat kezdése</string> - <string name="connect_plan_you_are_already_connecting_via_this_one_time_link">Csatlakozásod már folyamatban van ezzel az Egyszer használatos link-el!</string> - <string name="you_wont_lose_your_contacts_if_delete_address">Nem fogod elveszíteni az ismerőseidet ha később törlöd a SimpleX azonosítódat.</string> - <string name="updating_settings_will_reconnect_client_to_all_servers">A beállítások frissítése a szerverekhez újra kapcsolódással jár.</string> + <string name="you_will_still_receive_calls_and_ntfs">Továbbra is kap hívásokat és értesítéseket a némított profiloktól, ha azok aktívak.</string> + <string name="group_main_profile_sent">A fő csevegési profilja megküldésre kerül a csoporttagok számára.</string> + <string name="you_can_enable_delivery_receipts_later_alert">Később engedélyezheti őket az alkalmazás Adatvédelem és biztonság menüpontban.</string> + <string name="to_reveal_profile_enter_password">Rejtett profiljának felfedéséhez írja be a teljes jelszót a Csevegési profilok oldal keresőmezőjébe.</string> + <string name="upgrade_and_open_chat">A csevegés frissítése és megnyitása</string> + <string name="you_need_to_allow_to_send_voice">Hangüzeneteket küldéséhez engedélyeznie kell azok küldését az ismerősei számára.</string> + <string name="you_control_servers_to_receive_your_contacts_to_send"><![CDATA[Beállíthatja, hogy mely kiszolgáló(ko)n keresztül <b>fogadja</b> az üzeneteket, ismerősöket – a kiszolgálók, amelyeket az üzenetküldéshez használ.]]></string> + <string name="connect_plan_you_are_already_in_group_vName"><![CDATA[Már a(z) <b>%1$s</b> csoport tagja.]]></string> + <string name="snd_conn_event_switch_queue_phase_completed">Azonosítója megváltoztatva</string> + <string name="v4_3_irreversible_message_deletion_desc">Ismerősei engedélyezhetik a teljes üzenet törlést.</string> + <string name="you_have_to_enter_passphrase_every_time">A jelmondatot minden alkalommal meg kell adnia, amikor az alkalmazás elindul - nem az eszközön kerül tárolásra.</string> + <string name="open_port_in_firewall_desc">Ha engedélyezni szeretné, hogy egy mobilalkalmazás csatlakozzon a számítógéphez, akkor nyissa meg ezt a portot a tűzfalában, ha engedélyezte azt</string> + <string name="your_profile_is_stored_on_your_device">Profilja, ismerősök és az elküldött üzenetek az eszközön kerülnek tárolásra.</string> + <string name="system_restricted_background_warn"><![CDATA[Az értesítések engedélyezéséhez 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> + <string name="this_string_is_not_a_connection_link">Ez a karakterlánc nem egy meghívó hivatkozás!</string> + <string name="to_start_a_new_chat_help_header">Új csevegés kezdése</string> + <string name="connect_plan_you_are_already_connecting_via_this_one_time_link">Már csatlakozik ezen az egyszer használatos hivatkozáson keresztül!</string> + <string name="you_wont_lose_your_contacts_if_delete_address">Nem veszíti el az ismerőseit, ha később törli az azonosítóját.</string> + <string name="updating_settings_will_reconnect_client_to_all_servers">A beállítások frissítése a kiszolgálókhoz való újra kapcsolódással jár.</string> <string name="contact_wants_to_connect_with_you">kapcsolatba akar lépni veled!</string> - <string name="snd_group_event_changed_role_for_yourself">a saját szerepkörödet megváltoztattad erre: %s</string> - <string name="you_can_start_chat_via_setting_or_by_restarting_the_app">A chat szolgáltatást elindíthatod a beállítások / adatbázis pontban vagy az app újraindításával.</string> - <string name="verify_code_on_mobile">Ellenőrizd a kódot a mobilon!</string> - <string name="youve_accepted_group_invitation_connecting_to_inviting_group_member">Csatlakoztál ehhez a csoporthoz. Kapcsolódás a meghívó csoporttaghoz.</string> - <string name="you_can_connect_to_simplex_chat_founder"><![CDATA[Kapcsolatba léphetsz <font color="#0088ff">a SimpleX Chat fejlesztőivel és kérdezhetsz bármit és értesülhetsz az újdonságokról</font>.]]></string> + <string name="snd_group_event_changed_role_for_yourself">Saját szerepköre erre változott: %s</string> + <string name="you_can_start_chat_via_setting_or_by_restarting_the_app">A csevegési szolgáltatás elindítható a Beállítások / Adatbázis menüpontban vagy az alkalmazás újraindításával.</string> + <string name="verify_code_on_mobile">Kód ellenőrzése a mobilon</string> + <string name="youve_accepted_group_invitation_connecting_to_inviting_group_member">Csatlakozott ehhez a csoporthoz. Kapcsolódás a meghívó csoporttaghoz.</string> + <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">Opcionális üdvözlő üzenettel.</string> <string name="unknown_database_error_with_info">Ismeretlen adatbázis hiba: %s</string> - <string name="you_can_hide_or_mute_user_profile">Elrejtheted vagy némíthatod egy felhasználó profilját - tartsd lenyomva a menühöz!</string> + <string name="you_can_hide_or_mute_user_profile">Elrejthet vagy némíthat egy felhasználói profilt - tartsa lenyomva a menühöz!</string> <string name="v5_3_simpler_incognito_mode_descr">Inkognító mód csatlakozáskor</string> - <string name="update_onion_hosts_settings_question">TOR .onion host beállítások frissítése?</string> - <string name="you_can_share_group_link_anybody_will_be_able_to_connect">Megoszthatsz egy linket vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Csoporttagokat nem fogsz elveszíteni, az esetben sem ha kásőbb törlöd a csoportot.</string> - <string name="you_joined_this_group">Csatlakoztál ehhez a csoporthoz</string> - <string name="connect_plan_this_is_your_link_for_group_vName"><![CDATA[Ez a linked a(z) <b>%1$s</b> csoporthoz!]]></string> + <string name="update_onion_hosts_settings_question">Tor .onion host beállítások frissítése?</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> + <string name="connect_plan_this_is_your_link_for_group_vName"><![CDATA[Ez a hivatkozása a(z) <b>%1$s</b> csoporthoz!]]></string> <string name="voice_prohibited_in_this_chat">A hangüzenetek le vannak tiltva ebben a csevegésben.</string> - <string name="you_control_your_chat">Te tartod kézben chatedet!</string> + <string name="you_control_your_chat">Ön irányítja csevegését!</string> <string name="verify_code_with_desktop">Kód ellenőrzése a számítógépen</string> - <string name="v4_5_private_filenames_descr">Az időzóna, kép/hang fájlok megvédése érdekében használd az UTC-t!</string> - <string name="connect_via_member_address_alert_desc">A meghívó elküldésre kerül a csoporttag számára.</string> - <string name="incognito_info_share">Amikor megosztasz egy inkognító profilt valakivel, az a profil lesz használva a csoporthoz is amibe meghív.</string> - <string name="connect_plan_you_have_already_requested_connection_via_this_address">Már küldtél meghívót ezen az azonosítón keresztül!</string> - <string name="you_can_share_this_address_with_your_contacts">Megoszthatod ezt a SimpleX azonosítót az ismerőseiddel, hogy kapcsolatba léphessenek %s-el .</string> - <string name="you_can_accept_or_reject_connection">Amikor meghívót kapsz emberektől, elfogadhatod vagy elutasíthatod azokat!</string> - <string name="v4_6_group_welcome_message_descr">Állítsd be az új tagoknak megjelenő üzenetet!</string> + <string name="v4_5_private_filenames_descr">Az időzóna védelme érdekében a kép-/hangfájlok UTC-t használnak.</string> + <string name="connect_via_member_address_alert_desc">Csoporttag részére a csatlakozási kérelem eküldésre kerül.</string> + <string name="incognito_info_share">Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott.</string> + <string name="connect_plan_you_have_already_requested_connection_via_this_address">Már kért egy csatlakozást ezen az azonosítón keresztül!</string> + <string name="you_can_share_this_address_with_your_contacts">Megoszthatja ezt a SimpleX azonosítót ismerőseivel, hogy kapcsolatba léphessenek ezzel: %s.</string> + <string name="you_can_accept_or_reject_connection">Csatlakozási kérelmek esetében, elfogadhatja vagy elutasíthatja azokat.</string> + <string name="v4_6_group_welcome_message_descr">Megjelenő üzenetet beállítása új tagok részére!</string> <string name="whats_new_thanks_to_users_contribute_weblate">Köszönet a felhasználóknak - hozzájárulás a Weblaten!</string> - <string name="sending_delivery_receipts_will_be_enabled">A kézbesítési jelentés küldése minden kapcsolat számára engedélyezve lesz.</string> + <string name="sending_delivery_receipts_will_be_enabled">A kézbesítési jelentés küldése minden ismerős 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="database_backup_can_be_restored">Az adatbázis jelmondatának 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 profilod csak az ismerőseid számára kerül megosztásra.</string> - <string name="smp_servers_test_some_failed">Néhány szerver megbukott a teszten:</string> - <string name="group_invitation_tap_to_join">Érintsd meg a csatlakozáshoz</string> + <string name="profile_is_only_shared_with_your_contacts">Profilja csak az ismerősei számára 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="delete_files_and_media_desc">Ez a művelet nem vonható vissza - az összes fogadott és küldött fájl a médiatartalommal együtt törlésre kerülnek. Az alacsony felbontású fotók viszont megmaradnak.</string> - <string name="receipts_contacts_override_enabled">A kézbesítési jelentés engedélyezve van %d ismerősödnél</string> + <string name="receipts_contacts_override_enabled">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> <string name="v5_0_polish_interface_descr">Köszönet a felhasználóknak - hozzájárulás a Weblaten!</string> - <string name="sending_delivery_receipts_will_be_enabled_all_profiles">A kézbesítési jelentés küldése az összes látható csevegőprofilban lévő összes ismerős számára engedélyezve lesz.</string> + <string name="sending_delivery_receipts_will_be_enabled_all_profiles">A kézbesítési jelentések küldése engedélyezésre kerül az összes látható csevegési profilban lévő minden ismerős számára.</string> <string name="v4_6_audio_video_calls_descr">Bluetooth támogatás és egyéb fejlesztések.</string> - <string name="in_developing_desc">Ez a funkció még nem támogatott. Próbáld meg a következő kiadásban.</string> + <string name="in_developing_desc">Ez a funkció még nem támogatott. Próbálja meg a következő kiadásban.</string> <string name="share_text_updated_at">A bejegyzés frissítve: %s</string> <string name="skip_inviting_button">Tagok meghívásának kihagyása</string> <string name="receipts_section_description_1">Ezek felülbírálhatóak az ismerős- és csoportbeállításokban.</string> - <string name="contact_you_shared_link_with_wont_be_able_to_connect">Az ismerős, akivel megosztottad ezt a linket, NEM fog tudni csatlakozni!</string> + <string name="contact_you_shared_link_with_wont_be_able_to_connect">Ismerőse NEM fog tudni csatlakozni, akivel megosztotta ezt a hivatkozást!</string> <string name="you_can_change_it_later">A véletlenszerű jelmondat egyszerű szövegként van tárolva a beállításokban. -\nKésőbb megváltoztathatod.</string> - <string name="group_invitation_tap_to_join_incognito">Érintsd meg az inkognitóban való csatlakozáshoz</string> +\nEz később megváltoztatható.</string> + <string name="group_invitation_tap_to_join_incognito">Koppintson az inkognitómódhoz való csatlakozáshoz</string> <string name="set_password_to_export">Jelmondat beállítása az exportáláshoz</string> - <string name="receipts_groups_override_disabled">Az üzenet kézbesítési jelentés le van tiltva %d csoportnál</string> - <string name="non_fatal_errors_occured_during_import">Néhány nem végzetes hiba történt az importálás során - további részletekért lásd a Chat-konzolt.</string> + <string name="receipts_groups_override_disabled">Kézbesítési jelentések le vannak tiltva a(z) %d csoportban</string> + <string name="non_fatal_errors_occured_during_import">Néhány nem végzetes hiba történt az importálás során – további részletekért a csevegési konzolban olvashat.</string> <string name="v4_5_italian_interface_descr">Köszönet a felhasználóknak - hozzájárulás a Weblaten!</string> - <string name="relay_server_if_necessary">A relé szerver csak végszükség esetén használatos. Egy másik fél megfigyelheti az IP-címedet.</string> - <string name="v5_0_app_passcode_descr">Állítsd be a rendszerhitelesítés helyett.</string> - <string name="switch_receiving_address_desc">A fogadó cím egy másik szerverre változik. A címváltoztatás a feladó online állapotba kerülése után fejeződik be.</string> - <string name="stop_chat_to_export_import_or_delete_chat_database">A csevegés leállítása a csevegőadatbázis exportálásához, importálásához vagy törléséhez. A csevegés leállítása alatt nem tudsz üzeneteket fogadni és küldeni.</string> - <string name="save_passphrase_in_keychain">Jelmondat mentése a Kulcstárban</string> + <string name="relay_server_if_necessary">Az átjátszó kiszolgáló csak szükség esetén kerül használatra. Egy másik fél megfigyelheti az IP-címét.</string> + <string name="v5_0_app_passcode_descr">Rendszerhitelesítés helyetti beállítás.</string> + <string name="switch_receiving_address_desc">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.</string> + <string name="stop_chat_to_export_import_or_delete_chat_database">A csevegés leállítása a csevegő adatbázis exportálásához, importálásához, vagy törléséhez. A csevegés leállítása alatt nem tud üzeneteket fogadni és küldeni.</string> + <string name="save_passphrase_in_keychain">Jelmondat mentése a kulcstárolóban</string> <string name="v4_6_chinese_spanish_interface_descr">Köszönet a felhasználóknak - hozzájárulás a Weblaten!</string> <string name="save_passphrase_in_settings">Jelmondat mentése a beállításokban</string> <string name="send_receipts_disabled_alert_msg">Ennek a csoportnak több mint %1$d tagja van, a kézbesítési jelentések nem kerülnek elküldésre.</string> <string name="v5_2_message_delivery_receipts_descr">A második jelölés, amit kihagytunk! ✅</string> - <string name="relay_server_protects_ip">A relay szerver védi az IP-címedet, de megfigyelheti a hívás időtartamát.</string> + <string name="relay_server_protects_ip">Az átjátszó kiszolgáló megvédi IP-címét, de megfigyelheti a hívás időtartamát.</string> <string name="read_more_in_github">További információ a GitHub tárolónkban.</string> <string name="v4_5_message_draft_descr">Az utolsó üzenet tervezetének megőrzése a mellékletekkel együtt.</string> <string name="saved_ICE_servers_will_be_removed">A mentett WebRTC ICE-kiszolgálók eltávolításra kerülnek.</string> - <string name="receipts_groups_override_enabled">A kézbesítési jelentés engedélyezve van %d csoportnál</string> - <string name="member_role_will_be_changed_with_notification">A szerepkör \"%s\"-re fog változni. A csoportban mindenki értesítést kap.</string> - <string name="users_delete_with_connections">Profil és szerverkapcsolatok</string> - <string name="the_messaging_and_app_platform_protecting_your_privacy_and_security">A Te adatvédelmedet és biztonságodat védő üzenetküldő és alkalmazásplatform.</string> - <string name="tap_to_activate_profile">Érintsd meg a profil aktiválásához.</string> - <string name="receipts_contacts_override_disabled">Az üzenet kézbesítési jelentés le van tiltva %d ismerősödnél</string> + <string name="receipts_groups_override_enabled">Kézbesítési jelentések engedélyezve vannak a(z) %d csoportban</string> + <string name="member_role_will_be_changed_with_notification">A szerepkör meg fog változni erre: \"%s\". A csoportban mindenki értesítve lesz.</string> + <string name="users_delete_with_connections">Profil és kiszolgálókapcsolatok</string> + <string name="the_messaging_and_app_platform_protecting_your_privacy_and_security">Üzenetküldő és alkalmazásplatform, amely védi az ön adatvédelmét és biztonságát.</string> + <string name="tap_to_activate_profile">A profil aktiválásához koppintson az ikonra.</string> + <string name="receipts_contacts_override_disabled">Kézbesítési jelentések le vannak tiltva %d ismerősnél</string> <string name="session_code">Munkamenet kód</string> <string name="v4_4_french_interface_descr">Köszönet a felhasználóknak - hozzájárulás a Weblaten!</string> <string name="receipts_section_groups">Kis csoportok (max. 20 tag)</string> - <string name="connection_you_accepted_will_be_cancelled">Az általad elfogadott kapcsolat törlésre kerül!</string> - <string name="send_live_message_desc">Élő üzenet küldése - a címzett(ek) számára frissül, miközben írod az üzenetet.</string> + <string name="connection_you_accepted_will_be_cancelled">Az ön által elfogadott kapcsolat megszakad!</string> + <string name="send_live_message_desc">Élő üzenet küldése - a címzett(ek) számára frissül, ahogy beírja</string> <string name="settings_section_title_delivery_receipts">A KÉZBESÍTÉSI JELENTÉSEKET A KÖVETKEZŐ CÍMRE KELL KÜLDENI</string> <string name="alert_text_msg_bad_id">A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel). \nEz valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő.</string> <string name="this_device_name_shared_with_mobile">Az eszköz neve megosztásra kerül a csatlakoztatott mobil klienssel.</string> <string name="v4_4_live_messages_desc">A címzettek a beírás közben látják a frissítéseket.</string> - <string name="store_passphrase_securely">Kérjük, hogy a jelmondatot biztonságosan tárold, ha elveszíted, NEM tudod megváltoztatni.</string> - <string name="passphrase_will_be_saved_in_settings">A jelmondat a beállításokban egyszerű szövegként tárolódik, miután megváltoztattad vagy újraindítottad az alkalmazást.</string> - <string name="smp_servers_per_user">Jelenlenlegi profilod új ismerőseinek kiszolgálói</string> - <string name="receiving_via">Fogadás a</string> - <string name="store_passphrase_securely_without_recover">Kérjük, hogy a jelmondatot biztonságosan tárold, ha elveszíted, NEM fogsz tudni hozzáférni a chathez.</string> + <string name="store_passphrase_securely">Tárolja el biztonságosan jelmondatát, mert ha elveszíti azt, NEM tudja megváltoztatni.</string> + <string name="passphrase_will_be_saved_in_settings">A jelmondat a beállítások között egyszerű szövegként kerül tárolásra, miután megváltoztatta vagy újraindította az alkalmazást.</string> + <string name="smp_servers_per_user">A jelenlegi csevegési profilhoz tartozó új kapcsolatok kiszolgálói</string> + <string name="receiving_via">Fogadás ezen keresztül:</string> + <string name="store_passphrase_securely_without_recover">Tárolja el biztonságosan jelmondát, mert ha elveszti azt, akkor NEM férhet hozzá a csevegéshez.</string> <string name="member_role_will_be_changed_with_invitation">A szerepkör \"%s\"-re fog változni. A tag új meghívót kap.</string> <string name="icon_descr_profile_image_placeholder">profilkép helyőrző</string> <string name="sync_connection_force_desc">A titkosítás működik, és új titkosítási egyezményre nincs szükség. Ez kapcsolati hibákat eredményezhet!</string> - <string name="delete_chat_profile_action_cannot_be_undone_warning">Ez a művelet nem vonható vissza - profilod, ismerőseid, üzeneteid és fájljaid visszafordíthatatlanul törlésre kerülnek.</string> + <string name="delete_chat_profile_action_cannot_be_undone_warning">Ez a művelet nem vonható vissza - profilok, ismerősök, üzenetek és fájlok visszafordíthatatlanul törlésre kerülnek.</string> <string name="info_row_updated_at">A bejegyzés frissítve</string> <string name="read_more_in_user_guide_with_link"><![CDATA[További információ a <font color="#0088ff">Felhasználói útmutatóban</font> olvasható.]]></string> <string name="settings_is_storing_in_clear_text">A jelmondat a beállításokban egyszerű szövegként van tárolva.</string> <string name="terminal_always_visible">Konzol megjelenítése új ablakban</string> <string name="alert_text_msg_bad_hash">Az előző üzenet hash-e más.</string> - <string name="receipts_section_description">Ezek a beállítások a jelenlegi profilodra vonatkoznak</string> - <string name="loading_remote_file_desc">Kérjük, várj, amíg a fájl betöltődik az összekapcsolt mobilról.</string> + <string name="receipts_section_description">Ezek a beállítások a jelenlegi profiljára vonatkoznak</string> + <string name="loading_remote_file_desc">Várjon, amíg a fájl betöltődik a csatolt mobilról</string> <string name="read_more_in_github_with_link"><![CDATA[További információ a <font color="#0088ff">GitHub tárolónkban</font>.]]></string> <string name="error_showing_content">hiba a tartalom megjelenítése közben</string> <string name="error_showing_message">hiba az üzenet megjelenítésekor</string> - <string name="you_can_make_address_visible_via_settings">Láthatóvá teheted a SimpleX ismerőseid számára a Beállításokban.</string> + <string name="you_can_make_address_visible_via_settings">Láthatóvá teheti SimpleX beli ismerősei számára a Beállításokban.</string> <string name="recent_history_is_sent_to_new_members">Legfeljebb az utolsó 100 üzenet kerül elküldésre az új tagoknak.</string> - <string name="code_you_scanned_is_not_simplex_link_qr_code">A beolvasott kód nem egy SimpleX link QR-kód.</string> - <string name="the_text_you_pasted_is_not_a_link">A beillesztett szöveg nem SimpleX link.</string> - <string name="you_can_view_invitation_link_again">A meghívó linket újra megtekintheti a kapcsolat részletei között.</string> + <string name="code_you_scanned_is_not_simplex_link_qr_code">A beolvasott kód nem egy SimpleX hivatkozás QR-kód.</string> + <string name="the_text_you_pasted_is_not_a_link">A beillesztett szöveg nem egy SimpleX hivatkozás.</string> + <string name="you_can_view_invitation_link_again">A meghívó hivatkozását újra megtekintheti a kapcsolat részleteinél.</string> <string name="start_chat_question">Csevegés indítása?</string> <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">Kapcsolat hozzáadása</string> - <string name="tap_to_scan">Koppints a beolvasáshoz</string> - <string name="tap_to_paste_link">Koppints 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ó link létrehozásához, vagy egy kapott linken keresztül történő csatlakozáshoz.]]></string> - <string name="create_group_button_to_create_new_group"><![CDATA[<b>Új csoport létrehozása</b>: új csoport létrehozásához.]]></string> - <string name="chat_is_stopped_you_should_transfer_database">A csevegés leállt. Ha már használtad ezt az adatbázist egy másik eszközön, a csevegés megkezdése előtt vissza kell állítanod.</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_paste_link">Koppintson 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ő csatlakozá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> + <string name="chat_is_stopped_you_should_transfer_database">A csevegés leállt. Ha már használta ezt az adatbázist egy másik eszközön, úgy visszaállítás szükséges a csevegés megkezdése előtt.</string> <string name="recent_history_is_not_sent_to_new_members">Az előzmények nem kerülnek elküldésre új tagok részére.</string> <string name="retry_verb">Újrapróbálkozás</string> - <string name="camera_not_available">A kamera nem elérhető</string> - <string name="enable_sending_recent_history">Utolsó 100 üzenet küldése új tagoknak.</string> - <string name="disable_sending_recent_history">Ne küldjön előzményeket új tagoknak.</string> + <string name="camera_not_available">A fényképező nem elérhető</string> + <string name="enable_sending_recent_history">Az utolsó 100 üzenet elküldése az új tagoknak.</string> + <string name="disable_sending_recent_history">Ne küldjön előzményeket az új tagok részére.</string> <string name="or_show_this_qr_code">Vagy mutassa meg ezt a kódot</string> <string name="enable_camera_access">Kamera hozzáférés engedélyezése</string> - <string name="keep_unused_invitation_question">Megtartod a fel nem használt meghívót?</string> - <string name="share_this_1_time_link">Egyszer használatos link megosztása</string> + <string name="keep_unused_invitation_question">Fel nem használt meghívó megtartása?</string> + <string name="share_this_1_time_link">Egyszer használatos meghívó hivatkozás megosztása</string> <string name="new_chat">Új beszélgetés</string> <string name="loading_chats">Csevegések betöltése…</string> - <string name="creating_link">Link létrehozása…</string> + <string name="creating_link">Hivatkozás létrehozása…</string> <string name="or_scan_qr_code">Vagy QR-kód beolvasása</string> <string name="invalid_qr_code">Érvénytelen QR-kód</string> - <string name="keep_invitation_link">Megtart</string> - <string name="search_or_paste_simplex_link">Keresés vagy SimpleX link beillesztése</string> + <string name="keep_invitation_link">Megtartás</string> + <string name="search_or_paste_simplex_link">Keresés, vagy SimpleX hivatkozás beillesztése</string> <string name="show_internal_errors">Belső hibák megjelenítése</string> <string name="agent_critical_error_title">Kritikus hiba</string> <string name="agent_internal_error_title">Belső hiba</string> - <string name="remote_ctrl_error_bad_version">Az asztali kliens verziója nem támogatott. Kérlek, győződj meg arról, hogy mindkét eszközön ugyanazt a verziót használod</string> - <string name="remote_ctrl_error_bad_invitation">Az asztali kliensen hibás meghívókód szerepel</string> - <string name="remote_ctrl_error_busy">Az asztali kliens elfoglalt</string> - <string name="remote_ctrl_error_inactive">Az asztali kliens inaktív</string> + <string name="remote_ctrl_error_bad_version">Nem támogatott számítógép kliens verzió. Győződjön meg arról, hogy mindkét eszközön ugyanazt a verziót használja</string> + <string name="remote_ctrl_error_bad_invitation">Számítógép kliensen hibás meghívókód szerepel</string> + <string name="remote_ctrl_error_busy">Számítógép elfoglalt</string> + <string name="remote_ctrl_error_inactive">Számítógép inaktív</string> <string name="restart_chat_button">Csevegés újraindítása</string> - <string name="remote_ctrl_error_timeout">Időtúllépés az asztali klienshez való kapcsolódás közben</string> - <string name="remote_ctrl_error_disconnected">Az asztali kliens kapcsolata megszakadt</string> + <string name="remote_ctrl_error_timeout">Időtúllépés a számítógépről való kapcsolódás közben</string> + <string name="remote_ctrl_error_disconnected">Számítógép kliens kapcsolata megszakadt</string> <string name="remote_host_was_disconnected_title">A kapcsolat megszakadt</string> <string name="remote_ctrl_was_disconnected_title">A kapcsolat megszakadt</string> - <string name="remote_ctrl_error_bad_state">Az asztali kliens kapcsolata rossz állapotban van</string> - <string name="agent_critical_error_desc">Kérjük, jelezd ezt a fejlesztőknek: + <string name="remote_ctrl_error_bad_state">Számítógép kapcsolata rossz állapotban van</string> + <string name="agent_critical_error_desc">Jelentse a fejlesztőknek: \n%s \n -\nJavasoljuk, hogy indítsd újra az alkalmazást.</string> - <string name="agent_internal_error_desc">Kérjük, jelezd a fejlesztőknek: +\nAz alkalmazás újraindítása javasolt.</string> + <string name="agent_internal_error_desc">Jelentse a fejlesztőknek: \n%s</string> - <string name="remote_host_error_bad_version"><![CDATA[A(z) <b>%s</b> mobil eszköz verziója nem támogatott. Kérlek, győződj meg róla, hogy mindkét eszközön ugyanazt a verziót használod]]></string> + <string name="remote_host_error_bad_version"><![CDATA[A(z) <b>%s</b> mobil eszköz verziója nem támogatott. Győződjön meg arról, hogy mindkét eszközön ugyanazt a verziót használja]]></string> <string name="remote_host_error_disconnected"><![CDATA[Megszakadt a kapcsolat a(z) <b>%s</b> mobil eszközzel]]></string> <string name="failed_to_create_user_invalid_title">Érvénytelen megjelenítendő felhaszálónév!</string> - <string name="failed_to_create_user_invalid_desc">Ez a megjelenített felhasználónév érvénytelen. Kérjük, válassz másikat.</string> - <string name="remote_host_disconnected_from"><![CDATA[Megszakadt a kapcsolat a <b>%s</b> mobil eszközzel, a(z) %s probléma miatt.]]></string> + <string name="failed_to_create_user_invalid_desc">Ez a megjelenített felhasználónév érvénytelen. Válasszon egy másik nevet.</string> + <string name="remote_host_disconnected_from"><![CDATA[Megszakadt a kapcsolat a <b>%s</b> mobil eszközzel, a(z) %s probléma miatt]]></string> <string name="remote_ctrl_disconnected_with_reason">%s probléma miatt megszakadt a kapcsolat</string> <string name="remote_host_error_missing"><![CDATA[A(z) <b>%s</b> mobil eszköz nem található]]></string> <string name="remote_host_error_bad_state"><![CDATA[A kapcsolat a(z) <b>%s</b> mobil eszközzel rossz állapotban van]]></string> @@ -1588,41 +1588,126 @@ <string name="remote_host_error_busy"><![CDATA[A(z) <b>%s</b> mobil eszköz elfoglalt]]></string> <string name="past_member_vName">Legutóbbi tag %1$s</string> <string name="group_member_status_unknown">ismeretlen státusz</string> - <string name="profile_update_event_member_name_changed">%1$s tag %2$s-ra/re változott</string> + <string name="profile_update_event_member_name_changed">%1$s tag neve megváltozott erre: %2$s</string> <string name="profile_update_event_removed_address">törölt csatlakozási cím</string> <string name="profile_update_event_removed_picture">törölt profilkép</string> <string name="profile_update_event_set_new_address">új kapcsolattartási cím beállítása</string> <string name="profile_update_event_set_new_picture">új profilkép beállítása</string> <string name="profile_update_event_updated_profile">frissített profil</string> - <string name="profile_update_event_contact_name_changed">%1$s ismerős %2$s-ra/re változott</string> + <string name="profile_update_event_contact_name_changed">%1$s ismerős neve megváltozott erre: %2$s</string> <string name="note_folder_local_display_name">Privát jegyzetek</string> <string name="error_deleting_note_folder">Hiba a privát jegyzetek törlésekor</string> <string name="error_creating_message">Hiba az üzenet létrehozásakor</string> - <string name="clear_note_folder_question">Törlöd a privát jegyzeteket?</string> + <string name="clear_note_folder_question">Privát jegyzetek törlése?</string> <string name="info_row_created_at">Létrehozva ekkor:</string> <string name="saved_message_title">Mentett üzenet</string> - <string name="share_text_created_at">Létrehozva ekkor: %s</string> - <string name="clear_note_folder_warning">Az összes üzenet törlődik – ez nem vonható vissza!</string> + <string name="share_text_created_at">Megosztva ekkor: %s</string> + <string name="clear_note_folder_warning">Minden üzenet törlésre kerül – ez nem vonható vissza!</string> <string name="v5_5_message_delivery">Továbbfejlesztett üzenetküldés</string> <string name="v5_5_join_group_conversation">Csatlakozás csoportos beszélgetésekhez</string> - <string name="v5_5_simpler_connect_ui">A link beillesztése a csatlakozáshoz!</string> + <string name="v5_5_simpler_connect_ui">Hivatkozás beillesztése a csatlakozáshoz!</string> <string name="v5_5_private_notes">Privát jegyzetek</string> <string name="v5_5_simpler_connect_ui_descr">A keresősáv fogadja a meghívó hivatkozásokat.</string> <string name="v5_5_private_notes_descr">Titkosított fájlokkal és médiatartalommal.</string> <string name="v5_5_message_delivery_descr">Csökkentett akkumulátorhasználattal.</string> <string name="v5_5_new_interface_languages">Magyar és török felhasználói felület</string> - <string name="v5_5_join_group_conversation_descr">A közelmúlt eseményei és továbbfejlesztett Jegyzék Bot.</string> + <string name="v5_5_join_group_conversation_descr">A közelmúlt eseményei és továbbfejlesztett jegyzék bot.</string> <string name="rcv_group_event_member_unblocked">%s letiltása feloldva</string> - <string name="snd_group_event_member_unblocked">feloldottad %s letiltását</string> + <string name="snd_group_event_member_unblocked">%s letiltását visszavonta</string> <string name="member_info_member_blocked">letiltva</string> <string name="blocked_by_admin_item_description">letiltva az admin által</string> <string name="member_blocked_by_admin">Letiltva az admin által</string> <string name="rcv_group_event_member_blocked">%s letiltva</string> <string name="block_for_all">Mindenki számára letiltva</string> - <string name="block_for_all_question">Mindenki számára letiltod ezt a tagot?</string> + <string name="block_for_all_question">Tag letiltása mindenki számára?</string> <string name="blocked_by_admin_items_description">%d üzenet letiltva az admin által</string> <string name="unblock_for_all">Letiltás feloldása mindenki számára</string> - <string name="unblock_for_all_question">Mindenki számára feloldod ennek a tagnak a letiltását?</string> - <string name="snd_group_event_member_blocked">letiltottad %s-t</string> + <string name="unblock_for_all_question">Mindenki számára feloldja a tag letiltását?</string> + <string name="snd_group_event_member_blocked">letiltotta %s-t</string> <string name="error_blocking_member_for_all">Hiba a tag mindenki számára való letiltása során</string> + <string name="message_too_large">Az üzenet túl nagy</string> + <string name="welcome_message_is_too_long">Az üdvözlő üzenet túl hosszú</string> + <string name="database_migration_in_progress">Az adatbázis migrációja folyamatban van. +\nEz eltarthat néhány percig.</string> + <string name="call_service_notification_audio_call">Hanghívás</string> + <string name="call_service_notification_end_call">A hívás befejeződött</string> + <string name="call_service_notification_video_call">Videóhívás</string> + <string name="unable_to_open_browser_title">Hiba a böngésző megnyitása közben</string> + <string name="unable_to_open_browser_desc">A hívásokhoz egy alapértelmezett webböngésző szükséges. Állítson be egy alapértelmezett webböngészőt az eszközön, és osszon meg további információkat a SimpleX Chat fejlesztőivel.</string> + <string name="migrate_to_device_confirm_network_settings">Hálózati beállítások megerősítése</string> + <string name="migrate_from_device_error_exporting_archive">Hiba a csevegési adatbázis exportálásakor</string> + <string name="migrate_to_device_apply_onion">Alkalmaz</string> + <string name="migrate_from_device_archive_and_upload">Archiválás és feltöltés</string> + <string name="migrate_from_device_confirm_upload">Feltöltés megerősítése</string> + <string name="migrate_from_device_error_deleting_database">Hiba az adatbázis törlésekor</string> + <string name="v5_6_safer_groups_descr">Az adminok egy tagot mindenki számára letilthatnak.</string> + <string name="migrate_from_device_all_data_will_be_uploaded">Minden ismerős, a beszélgetések és a fájlok biztonságosan titkosításra kerülnek, melyek részletekben feltöltődnek a beállított XFTP átjátszókra.</string> + <string name="v5_6_app_data_migration">Alkalmazásadatok átköltöztetése</string> + <string name="migrate_from_device_archiving_database">Adatbázis archiválása</string> + <string name="migrate_from_device_cancel_migration">Az átköltöztetés megszakítása</string> + <string name="migrate_to_device_chat_migrated">A csevegés átköltöztetve!</string> + <string name="migrate_from_device_check_connection_and_try_again">Ellenőrizze az internetkapcsolatot, és próbálja újra</string> + <string name="migrate_from_device_creating_archive_link">Archív hivatkozás létrehozása</string> + <string name="migrate_from_device_delete_database_from_device">Adatbázis törlése erről az eszközről</string> + <string name="migrate_to_device_download_failed">Letöltés sikertelen</string> + <string name="migrate_to_device_downloading_archive">Archívum letöltése</string> + <string name="migrate_to_device_downloading_details">Letöltési hivatkozás részletei</string> + <string name="v5_6_quantum_resistant_encryption_descr">Engedélyezés a közvetlen csevegésekben (BÉTA)!</string> + <string name="migrate_to_device_enter_passphrase">Jelmondat megadása</string> + <string name="migrate_from_device_error_saving_settings">Hiba a beállítások mentésekor</string> + <string name="migrate_to_device_error_downloading_archive">Hiba az archívum letöltésekor</string> + <string name="migrate_from_device_error_uploading_archive">Hiba az archívum feltöltésekor</string> + <string name="migrate_from_device_error_verifying_passphrase">Hiba a jelmondat ellenőrzésekor:</string> + <string name="migrate_from_device_exported_file_doesnt_exist">Az exportált fájl nem létezik</string> + <string name="migrate_to_device_file_delete_or_link_invalid">A fájl törlésre került, vagy érvénytelen hivatkozás</string> + <string name="migrate_to_device_bytes_downloaded">%s letöltve</string> + <string name="migrate_to_device_importing_archive">Archívum importálása</string> + <string name="migrate_from_device_database_init">Feltöltés előkészítése</string> + <string name="migrate_from_device_verify_database_passphrase">Adatbázis jelmondatának ellenőrzése</string> + <string name="migrate_from_device_verify_passphrase">Jelmondat ellenőrzése</string> + <string name="set_passphrase">Jelmondat beállítása</string> + <string name="v5_6_picture_in_picture_calls">Kép a képben hívások</string> + <string name="v5_6_safer_groups">Biztonságosabb csoportok</string> + <string name="v5_6_picture_in_picture_calls_descr">Használja az alkalmazást hívás közben.</string> + <string name="or_paste_archive_link">Vagy illessze be az archívum hivatkozását</string> + <string name="paste_archive_link">Az archívum hivatkozásának beillesztése</string> + <string name="migrate_to_device_repeat_download">Letöltés ismét</string> + <string name="migrate_to_device_import_failed">Sikertelen importálás</string> + <string name="migrate_to_device_confirm_network_settings_footer">Ellenőrizze, hogy a hálózati beállítások megfelelőek-e ehhez az eszközhöz.</string> + <string name="migrate_from_device_chat_should_be_stopped">A folytatáshoz a csevegést le kell állítani.</string> + <string name="migrate_from_device_stopping_chat">Csevegés leállítása</string> + <string name="migrate_from_device_or_share_this_file_link">Vagy ossza meg biztonságosan ezt a fájl hivatkozást</string> + <string name="migrate_from_device_start_chat">Csevegés indítása</string> + <string name="migrate_from_device_you_must_not_start_database_on_two_device"><![CDATA[<b>Nem szabad</b> ugyanazt az adatbázist használni egyszerre két eszközön.]]></string> + <string name="migrate_from_device_confirm_you_remember_passphrase">Erősítse meg, hogy emlékszik az adatbázis jelmondatára az átköltöztetéshez.</string> + <string name="migrate_from_device_choose_migrate_from_another_device"><![CDATA[Válassza az <i>Átköltöztetés egy másik eszközről</i> opciót az új eszközön és szkennelje be a QR-kódot.]]></string> + <string name="migrate_from_device_finalize_migration">Az átköltöztetés véglegesítése</string> + <string name="migrate_to_device_finalize_migration">Az átköltöztetés véglegesítése egy másik eszközön.</string> + <string name="migrate_to_device_database_init">Letöltés előkészítése</string> + <string name="migrate_from_device_repeat_upload">Feltöltés ismét</string> + <string name="migrate_from_device_bytes_uploaded">%s feltöltve</string> + <string name="migrate_from_device_upload_failed">A feltöltés sikertelen</string> + <string name="migrate_from_device_uploading_archive">Archívum feltöltése</string> + <string name="migrate_from_device_starting_chat_on_multiple_devices_unsupported">Figyelmeztetés: a csevegés elindítása egyszerre több eszközön nem támogatott, továbbá üzenetkézbesítési hibákat okozhat</string> + <string name="migrate_to_device_repeat_import">Importálás ismét</string> + <string name="conn_event_disabled_pq">szabványos végpontok közötti titkosítás</string> + <string name="migrate_to_device_title">Átköltöztetés ide</string> + <string name="migrate_from_device_title">Eszköz átköltöztetése</string> + <string name="migrate_from_device_to_another_device">Átköltöztetés egy másik eszközre</string> + <string name="migrate_from_device_archive_will_be_deleted"><![CDATA[<b>Figyelem</b>: az archívum törlésre kerül.]]></string> + <string name="migrate_from_another_device">Átköltöztetés egy másik eszközről</string> + <string name="v5_6_quantum_resistant_encryption">Kvantumrezisztens titkosítás</string> + <string name="migrate_from_device_try_again">Megpróbálhatja még egyszer.</string> + <string name="migrate_from_device_migration_complete">Átköltöztetés befejezve</string> + <string name="v5_6_app_data_migration_descr">Átköltöztetés egy másik eszközre QR-kód használatával.</string> + <string name="migrate_to_device_migrating">Átköltöztetés</string> + <string name="migrate_from_device_using_on_two_device_breaks_encryption"><![CDATA[<b>Megjegyzés</b>: ha két eszközön is ugyanazt az adatbázist használja, akkor biztonsági védelemként megszakítja a kapcsolataiból érkező üzenetek visszafejtését.]]></string> + <string name="migrate_to_device_try_again">Megpróbálhatja még egyszer.</string> + <string name="invalid_file_link">Hibás hivatkozás</string> + <string name="conn_event_enabled_pq">végpontok közötti kvantumrezisztens titkosítás</string> + <string name="e2ee_info_no_pq_short">Ez a csevegés végpontok közötti titkosítással védett.</string> + <string name="auth_open_migration_to_another_device">A költöztetési párbeszédablak megnyitása</string> + <string name="e2ee_info_pq_short">Ez a csevegés végpontok közötti kvantumrezisztens tikosítással védett.</string> + <string name="e2ee_info_no_pq"><![CDATA[Az üzeneteket, fájlokat és hívásokat <b>végpontok közötti titkosítással</b> és sérülés utáni titkosságvédelemmel, visszautasítással és sérülés utáni helyreállítással védi.]]></string> + <string name="e2ee_info_pq"><![CDATA[Az üzeneteket, fájlokat és hívásokat <b>végpontok közötti kvantumrezisztens titkosítással</b> és sérülés utáni titkosságvédelemmel, visszautasítással és sérülés utáni helyreállítással védi.]]></string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml index 0ee9a6a625..41618606f7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -1355,7 +1355,7 @@ <string name="turn_off_system_restriction_button">Apri impostazioni app</string> <string name="system_restricted_background_in_call_desc">L\'app potrebbe venire chiusa dopo 1 minuto in secondo piano.</string> <string name="paste_the_link_you_received_to_connect_with_your_contact">Incolla il link che hai ricevuto per connetterti con il contatto…</string> - <string name="connect__your_profile_will_be_shared">Il tuo profilo %1$s verrà condiviso.</string> + <string name="connect__your_profile_will_be_shared">Verrà condiviso il tuo profilo %1$s.</string> <string name="connect__a_new_random_profile_will_be_shared">Verrà condiviso un nuovo profilo casuale.</string> <string name="disable_notifications_button">Disattiva le notifiche</string> <string name="system_restricted_background_in_call_title">Nessuna chiamata in secondo piano</string> @@ -1500,7 +1500,7 @@ <string name="v5_4_block_group_members">Blocca i membri dei gruppi</string> <string name="v5_4_incognito_groups_descr">Crea un gruppo usando un profilo casuale.</string> <string name="v5_4_link_mobile_desktop">Collega le app mobile e desktop! 🔗</string> - <string name="v5_4_link_mobile_desktop_descr">Tramite protocollo sicuro resistente alla quantistica.</string> + <string name="v5_4_link_mobile_desktop_descr">Tramite protocollo sicuro resistente al quantistico.</string> <string name="v5_4_block_group_members_descr">Per nascondere messaggi indesiderati.</string> <string name="v5_4_better_groups">Gruppi migliorati</string> <string name="v5_4_incognito_groups">Gruppi in incognito</string> @@ -1630,4 +1630,89 @@ <string name="block_for_all_question">Bloccare il membro per tutti?</string> <string name="error_blocking_member_for_all">Errore nel blocco del membro per tutti</string> <string name="snd_group_event_member_blocked">hai bloccato %s</string> + <string name="welcome_message_is_too_long">Il messaggio di benvenuto è troppo lungo</string> + <string name="message_too_large">Messaggio troppo grande</string> + <string name="database_migration_in_progress">Migrazione database in corso. +\nPuò richiedere qualche minuto.</string> + <string name="call_service_notification_audio_call">Chiamata audio</string> + <string name="call_service_notification_end_call">Termina chiamata</string> + <string name="call_service_notification_video_call">Videochiamata</string> + <string name="unable_to_open_browser_title">Errore di apertura del browser</string> + <string name="unable_to_open_browser_desc">Il browser predefinito è necessario per le chiamate. Configura il browser predefinito nel sistema, poi condividi più informazioni con gli sviluppatori.</string> + <string name="e2ee_info_no_pq_short">Questa chat è protetta da crittografia end-to-end.</string> + <string name="e2ee_info_pq_short">Questa chat è protetta da crittografia end-to-end resistente al quantistico.</string> + <string name="migrate_from_device_start_chat">Avvia chat</string> + <string name="migrate_to_device_title">Migra qui</string> + <string name="migrate_to_device_downloading_details">Scaricamento dettagli del link</string> + <string name="migrate_to_device_downloading_archive">Scaricamento archivio</string> + <string name="migrate_from_device_error_uploading_archive">Errore di invio dell\'archivio</string> + <string name="migrate_from_device_archive_and_upload">Archivia e carica</string> + <string name="v5_6_safer_groups_descr">Gli amministratori possono bloccare un membro per tutti.</string> + <string name="migrate_from_device_all_data_will_be_uploaded">Tutti i tuoi contatti, le conversazioni e i file verranno criptati in modo sicuro e caricati in blocchi sui relay XFTP configurati.</string> + <string name="v5_6_app_data_migration">Migrazione dati dell\'app</string> + <string name="migrate_to_device_apply_onion">Applica</string> + <string name="migrate_from_device_archiving_database">Archiviazione del database</string> + <string name="migrate_from_device_using_on_two_device_breaks_encryption"><![CDATA[<b>Nota bene</b>: usare lo stesso database su due dispositivi bloccherà la decifrazione dei messaggi dalle tue connessioni, come misura di sicurezza.]]></string> + <string name="migrate_from_device_cancel_migration">Annulla migrazione</string> + <string name="migrate_from_device_archive_will_be_deleted"><![CDATA[<b>Attenzione</b>: l\'archivio verrà eliminato.]]></string> + <string name="migrate_to_device_chat_migrated">Chat migrata!</string> + <string name="migrate_from_device_check_connection_and_try_again">Controlla la tua connessione internet e riprova</string> + <string name="migrate_from_device_choose_migrate_from_another_device"><![CDATA[Scegli <i>Migra da un altro dispositivo</i> sul nuovo dispositivo e scansione il codice QR]]></string> + <string name="migrate_to_device_confirm_network_settings">Conferma le impostazioni di rete</string> + <string name="migrate_from_device_confirm_you_remember_passphrase">Conferma che ricordi la password del database da migrare.</string> + <string name="migrate_from_device_confirm_upload">Conferma caricamento</string> + <string name="migrate_from_device_creating_archive_link">Creazione link dell\'archivio</string> + <string name="migrate_from_device_delete_database_from_device">Elimina il database da questo dispositivo</string> + <string name="migrate_to_device_download_failed">Scaricamento fallito</string> + <string name="v5_6_quantum_resistant_encryption_descr">Attivala nelle chat dirette (BETA)!</string> + <string name="migrate_to_device_enter_passphrase">Inserisci password</string> + <string name="migrate_from_device_error_deleting_database">Errore di eliminazione del database</string> + <string name="migrate_to_device_error_downloading_archive">Errore di scaricamento dell\'archivio</string> + <string name="migrate_from_device_error_exporting_archive">Errore di esportazione del database della chat</string> + <string name="migrate_from_device_error_saving_settings">Errore di salvataggio delle impostazioni</string> + <string name="migrate_from_device_error_verifying_passphrase">Errore di verifica della password:</string> + <string name="migrate_from_device_exported_file_doesnt_exist">Il file esportato non esiste</string> + <string name="migrate_to_device_file_delete_or_link_invalid">Il file è stato eliminato o il link non è valido</string> + <string name="migrate_to_device_import_failed">Importazione fallita</string> + <string name="migrate_from_device_finalize_migration">Finalizza la migrazione</string> + <string name="migrate_from_device_chat_should_be_stopped">Per continuare, la chat deve essere fermata.</string> + <string name="migrate_to_device_finalize_migration">Finalizza la migrazione su un altro dispositivo.</string> + <string name="migrate_to_device_importing_archive">Importazione archivio</string> + <string name="invalid_file_link">Link non valido</string> + <string name="e2ee_info_no_pq"><![CDATA[I messaggi, i file e le chiamate sono protetti da <b>crittografia end-to-end</b> con perfect forward secrecy, ripudio e recupero da intrusione.]]></string> + <string name="e2ee_info_pq"><![CDATA[I messaggi, i file e le chiamate sono protetti da <b>crittografia e2e resistente al quantistico</b> con perfect forward secrecy, ripudio e recupero da intrusione.]]></string> + <string name="migrate_from_device_title">Migra dispositivo</string> + <string name="migrate_from_another_device">Migra da un altro dispositivo</string> + <string name="migrate_from_device_to_another_device">Migra ad un altro dispositivo</string> + <string name="v5_6_app_data_migration_descr">Migra ad un altro dispositivo via codice QR.</string> + <string name="migrate_to_device_migrating">Migrazione</string> + <string name="migrate_from_device_migration_complete">Migrazione completata</string> + <string name="auth_open_migration_to_another_device">Apri la schermata di migrazione</string> + <string name="or_paste_archive_link">O incolla il link dell\'archivio</string> + <string name="migrate_from_device_or_share_this_file_link">O condividi in modo sicuro questo link del file</string> + <string name="paste_archive_link">Incolla link dell\'archivio</string> + <string name="v5_6_picture_in_picture_calls">Chiamate picture-in-picture</string> + <string name="migrate_to_device_confirm_network_settings_footer">Conferma che le impostazioni di rete sono corrette per questo dispositivo.</string> + <string name="migrate_from_device_database_init">Preparazione del caricamento</string> + <string name="conn_event_enabled_pq">crittografia e2e resistente al quantistico</string> + <string name="migrate_to_device_repeat_import">Ripeti importazione</string> + <string name="migrate_to_device_database_init">Preparazione dello scaricamento</string> + <string name="v5_6_quantum_resistant_encryption">Crittografia resistente al quantistico</string> + <string name="migrate_to_device_repeat_download">Ripeti scaricamento</string> + <string name="migrate_from_device_repeat_upload">Ripeti caricamento</string> + <string name="v5_6_safer_groups">Gruppi più sicuri</string> + <string name="migrate_to_device_bytes_downloaded">%s scaricati</string> + <string name="conn_event_disabled_pq">crittografia end-to-end standard</string> + <string name="set_passphrase">Imposta password</string> + <string name="migrate_from_device_stopping_chat">Arresto della chat</string> + <string name="migrate_from_device_bytes_uploaded">%s caricati</string> + <string name="migrate_from_device_upload_failed">Invio fallito</string> + <string name="migrate_from_device_uploading_archive">Invio dell\'archivio</string> + <string name="v5_6_picture_in_picture_calls_descr">Usa l\'app mentre sei in chiamata.</string> + <string name="migrate_from_device_verify_passphrase">Verifica password</string> + <string name="migrate_from_device_verify_database_passphrase">Verifica password del database</string> + <string name="migrate_from_device_starting_chat_on_multiple_devices_unsupported">Attenzione: avviare la chat su più dispositivi non è supportato e provocherà problemi di recapito dei messaggi</string> + <string name="migrate_from_device_you_must_not_start_database_on_two_device"><![CDATA[<b>Non devi</b> usare lo stesso database su due dispositivi.]]></string> + <string name="migrate_to_device_try_again">Puoi fare un altro tentativo.</string> + <string name="migrate_from_device_try_again">Puoi fare un altro tentativo.</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index 6781899b21..7ab087612b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -794,7 +794,7 @@ <string name="updating_settings_will_reconnect_client_to_all_servers">設定を更新すると、全サーバにクライントの再接続が行われます。</string> <string name="save_color">色を保存</string> <string name="chat_preferences_you_allow">あなたが次を許可しています:</string> - <string name="chat_preferences_yes">はい</string> + <string name="chat_preferences_yes">オン</string> <string name="set_group_preferences">グループ設定を行う</string> <string name="your_preferences">あなたの設定</string> <string name="voice_prohibited_in_this_chat">このチャットでは音声メッセージが使用禁止です。</string> @@ -1346,7 +1346,7 @@ <string name="receipts_groups_disable_for_all">すべてのグループで無効にする</string> <string name="privacy_message_draft">メッセージの下書き</string> <string name="privacy_show_last_messages">最新のメッセージを表示</string> - <string name="send_receipts">配信通知の送信</string> + <string name="send_receipts">送信完了表示</string> <string name="rcv_group_event_n_members_connected">%s, %s および %d 人の他のメンバーが接続しています。</string> <string name="rcv_group_event_3_members_connected">%s, %s と %s は接続中</string> <string name="in_developing_desc">この機能はまだサポートされていません。次のリリースをお試しください。</string> diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index a8d6af6d40..57ce7eb06e 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -362,7 +362,7 @@ <string name="button_add_members">Pakviesti narius</string> <string name="disappearing_prohibited_in_this_chat">Išnykstančios žinutės šiame pokalbyje yra uždraustos.</string> <string name="voice_messages_are_prohibited">Balso žinutės šioje grupėje yra uždraustos.</string> - <string name="connect_via_group_link">Prisijungti per grupės nuorodą\?</string> + <string name="connect_via_group_link">Prisijungti prie grupės?</string> <string name="connect_via_contact_link">Prisijungti per adresato adresą?</string> <string name="sending_files_not_yet_supported">failų siuntimas kol kas nepalaikomas</string> <string name="invalid_message_format">neteisingas žinutės formatas</string> @@ -521,78 +521,73 @@ <string name="v5_0_large_files_support">Vaizdo įrašai ir failai iki 1GB</string> <string name="search_verb">Ieškoti</string> <string name="la_mode_off">Išjungta</string> - <string name="network_session_mode_user_description"><![CDATA[Atskiras TCP ryšys (ir SOCKS kredencialas) bus naudojamas <b>kiekvienam pokalbių profiliui, kurį turite programoje</b>.]]></string> - <string name="alert_text_decryption_error_n_messages_failed_to_decrypt">Nepavyko iššifruoti %1$d pranešimų.</string> - <string name="button_add_welcome_message">Pridėti sveikinimo pranešimą</string> + <string name="network_session_mode_user_description"><![CDATA[<b>Kiekvienam programėlėje esančiam pokalbių profiliui</b> bus naudojamas atskiras TCP ryšys (ir SOCKS prisijungimo duomenys).]]></string> + <string name="alert_text_decryption_error_n_messages_failed_to_decrypt">Nepavyko iššifruoti %1$d žinučių.</string> + <string name="button_add_welcome_message">Pridėti sveikinimo žinutę</string> <string name="v5_2_more_things">Dar keletas dalykų</string> <string name="all_group_members_will_remain_connected">Visi grupės nariai liks prisijungę.</string> - <string name="authentication_cancelled">Autentiškumo patvirtinimas atšauktas</string> - <string name="always_use_relay">Visada naudoti relę</string> - <string name="icon_descr_asked_to_receive">Paprašė leidimo gauti nuotrauką</string> - <string name="send_disappearing_message_5_minutes">5 minučių</string> - <string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>Mažiausiai bateriją eikvojantis variantas</b>. Jūs gausite sistemos pranešimus tik kai bus atidaryta programėlė (NEBUS fone veikiančios paslaugos).]]></string> - <string name="abort_switch_receiving_address">Atšaukti adreso keitimą</string> - <string name="v4_2_auto_accept_contact_requests">Automatiškai priimti susisiekimo užklausas</string> - <string name="v5_1_self_destruct_passcode_descr">Kai jį suvedate visi duomenys yra pašalinami.</string> - <string name="network_session_mode_entity_description">Atskiras TCP ryšys (ir SOCKS kredencialas) bus naudojamas <b>kiekvienam adresatui ir grupės nariui</b>. -\n<b>Prašome atkreipti dėmesį</b>: jei turite daug atskirų ryšių, akumuliatoriaus ir interneto sąnaudos gali būti žymiai didesnės, kartais ryšio gali visiškai nepavykti užmegzti.</string> + <string name="authentication_cancelled">Tapatybės patvirtinimo atsisakyta</string> + <string name="always_use_relay">Visada naudoti retransliavimą</string> + <string name="send_disappearing_message_5_minutes">5 minutės</string> + <string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>Mažiausios akumuliatoriaus sąnaudos</b>. Jūs gausite pranešimus tik tada, kai programėlė bus atverta (BE foninės tarnybos).]]></string> + <string name="abort_switch_receiving_address">Nutraukti adreso keitimą</string> + <string name="v4_2_auto_accept_contact_requests">Automatiškai priimti adresatų užklausas</string> + <string name="v5_1_self_destruct_passcode_descr">Jį įrašius visi duomenys bus pašalinti.</string> + <string name="network_session_mode_entity_description"><b>Kiekvienam adresatui ir grupės nariui</b> bus naudojamas atskiras TCP ryšys (ir SOCKS prisijungimo duomenys). +\n<b>Turėkite omenyje</b>: jei turite daug ryšių, akumuliatoriaus ir interneto duomenų sąnaudos gali būti žymiai didesnės ir, kartais, ryšiai gali patirti nesėkmę.</string> <string name="contact_wants_to_connect_via_call">%1$s nori su jumis susisiekti per</string> - <string name="turning_off_service_and_periodic">Yra įjungtas akumuliatoriaus naudojimo optimizavimas, kuris išjungia fone veikiančią paslaugą ir periodines naujų pranešimų užklausas. Juos iš naujo įjungti galite programos nustatymuose.</string> - <string name="notifications_mode_service">Visad įjungta</string> - <string name="notifications_mode_off_desc">Programėlė gaus sistemos pranešimus tik tada, kai ji bus įjungta, jokia fone veikianti paslauga nebus paleista</string> + <string name="turning_off_service_and_periodic">Yra įjungtas akumuliatoriaus naudojimo optimizavimas, išjungiantis foninę tarnybą ir periodines užklausas apie naujas žinutes. Nustatymuose galite įjungti ją iš naujo.</string> + <string name="notifications_mode_service">Visada įjungta</string> + <string name="notifications_mode_off_desc">Programėlė galės gauti sistemos pranešimus tik tada, kai veiks. Jokia foninė tarnyba nebus paleidžiama</string> <string name="accept_contact_button">Priimti</string> <string name="accept_contact_incognito_button">Priimti inkognito adresatą</string> - <string name="smp_servers_preset_add">Pridėti serverį iš sąrašo</string> - <string name="v4_2_group_links_desc">Administratoriai gali sukurti prisijungimo prie grupių nuorodas.</string> - <string name="notifications_mode_service_desc">Pastoviai fone veikianti paslauga. Sistemos pranešimai bus rodomi iš karto gavus žinutę.</string> - <string name="la_authenticate">Patvirtinti autentiškumą</string> - <string name="add_address_to_your_profile">Pridėkite adresą prie savo profilio, kad jūsų kontaktai galėtų bendrinti jį su kitais. Apie profilio atnaujinimą bus pranešta jūsų adresatams.</string> - <string name="icon_descr_video_asked_to_receive">Paprašė leidimo gauti vaizdo įrašą</string> - <string name="abort_switch_receiving_address_question">Ar norite atšaukti adreso keitimą\?</string> - <string name="abort_switch_receiving_address_desc">Adreso keitimas bus atšauktas. Bus naudojamas ankstusis gavimo adresas.</string> - <string name="send_disappearing_message_1_minute">1 minutės</string> + <string name="smp_servers_preset_add">Pridėti iš anksto parinktus serverius</string> + <string name="v4_2_group_links_desc">Administratoriai gali kurti prisijungimo prie grupių nuorodas.</string> + <string name="notifications_mode_service_desc">Foninė tarnyba visada veikia – pranešimai bus rodomi iš karto, kai tik bus prieinamos žinutės.</string> + <string name="la_authenticate">Patvirtinti tapatybę</string> + <string name="add_address_to_your_profile">Pridėkite adresą prie savo profilio, kad jūsų adresatai galėtų bendrinti jį su kitais žmonėmis. Apie profilio atnaujinimą bus pranešta jūsų adresatams.</string> + <string name="abort_switch_receiving_address_question">Nutraukti adreso keitimą?</string> + <string name="abort_switch_receiving_address_desc">Adreso keitimas bus nutrauktas. Bus naudojamas senas priėmimo adresas.</string> + <string name="send_disappearing_message_1_minute">1 minutė</string> <string name="icon_descr_audio_on">Įjungti garsą</string> <string name="icon_descr_audio_off">Išjungti garsą</string> - <string name="all_app_data_will_be_cleared">Visi programos duomenys yra ištrinami.</string> - <string name="app_passcode_replaced_with_self_destruct">Programos prieigos kodas pakeičiamas susinaikinimo prieigos kodu.</string> - <string name="empty_chat_profile_is_created">Sukuriamas tuščias pokalbių profilis nurodytu pavadinimu ir programėlė atsidaro kaip įprasta.</string> + <string name="all_app_data_will_be_cleared">Visi programėlės duomenys bus ištrinti.</string> + <string name="empty_chat_profile_is_created">Sukuriamas tuščias pokalbių profilis nurodytu pavadinimu ir programėlė atveriama kaip įprasta.</string> <string name="settings_section_title_app">PROGRAMĖLĖ</string> - <string name="keychain_is_storing_securely">Saugiai saugoti slaptažodžiui yra naudojamas \"Android Keystore\". Jis jums leidžia gauti sistemos pranešimus.</string> + <string name="keychain_is_storing_securely">Saugiam slaptafrazės saugojimui yra naudojama „Android Keystore“ – tai įgalina pranešimų tarnybą veikti.</string> <string name="color_secondary_variant">Papildoma antrinė spalva</string> - <string name="color_primary_variant">Papildoma pagrindinė spalva</string> + <string name="color_primary_variant">Papildomas akcentavimas</string> <string name="allow_to_send_files">Leisti siųsti failus ir mediją.</string> - <string name="abort_switch_receiving_address_confirm">Atšaukti</string> - <string name="alert_text_decryption_error_too_many_skipped">Praleista %1$d pranešimų.</string> + <string name="abort_switch_receiving_address_confirm">Nutraukti</string> + <string name="alert_text_decryption_error_too_many_skipped">Praleista %1$d žinučių.</string> <string name="all_your_contacts_will_remain_connected">Jūsų adresatai nepraras ryšio su jumis.</string> - <string name="callstatus_accepted">priimtas skambutis</string> + <string name="callstatus_accepted">atsilieptas skambutis</string> <string name="accept">Priimti</string> - <string name="integrity_msg_skipped">Praleistas (-i) %1$d pranešimas (-ai)</string> - <string name="auto_accept_images">Automatiškai priimti nuotraukas</string> - <string name="full_backup">Programos duomenų atsarginė kopija</string> - <string name="color_primary">Pagrindinė spalva</string> - <string name="keychain_allows_to_receive_ntfs">Saugiai saugoti slaptažodžiui, naujai paleidus programą ar jį pakeitus, naudojamas \"Android Keystore\". Jis jums leis gauti sistemos pranešimus.</string> + <string name="integrity_msg_skipped">Praleista žinučių: %1$d.</string> + <string name="full_backup">Programėlės duomenų atsarginė kopija</string> + <string name="color_primary">Akcentavimas</string> + <string name="keychain_allows_to_receive_ntfs">Paleidus programėlę iš naujo ar pakeitus slaptafrazę, saugiam slaptafrazės saugojimui bus naudojama „Android Keystore“ – tai leis gauti pranešimus.</string> <string name="color_background">Fonas</string> <string name="send_disappearing_message_30_seconds">30 sekundžių</string> - <string name="allow_message_reactions_only_if">Leisti reaguoti į pranešimus tik, jei tai leidžia jūsų adresatas.</string> + <string name="allow_message_reactions_only_if">Leisti reaguoti į žinutes tik tuo atveju, jeigu tai leidžia jūsų adresatas.</string> <string name="learn_more_about_address">Apie SimpleX adresą</string> - <string name="network_enable_socks_info">Ar norite prieiga prie serverių gauti per SOCKS tarpinį serverį naudojant prievadą %d\? Prieš įjungiant šią parinktį, tarpinis serveris turi būti paleistas.</string> - <string name="all_your_contacts_will_remain_connected_update_sent">Jūsų adresatai nepraras ryšio su jumis. Apie profilio pakeitimus bus pranešta jūsų adresatams.</string> + <string name="network_enable_socks_info">Ar gauti prieigą prie serverių per SOCKS įgaliotąjį serverį naudojant prievadą %d? Prieš įjungiant šią parinktį, privalo būti paleistas įgaliotasis serveris.</string> + <string name="all_your_contacts_will_remain_connected_update_sent">Jūsų adresatai nepraras ryšio su jumis. Apie profilio atnaujinimą bus pranešta jūsų adresatams.</string> <string name="audio_call_no_encryption">garso skambutis (nėra užšifruotas nuo vieno galo iki kito galo)</string> - <string name="group_info_section_title_num_members">%1$s NARIŲ</string> + <string name="group_info_section_title_num_members">NARIŲ: %1$s</string> <string name="address_section_title">Adresas</string> <string name="accept_feature">Priimti</string> - <string name="allow_calls_only_if">Leisti skambučius tik, jei juos leidžia jūsų adresatas.</string> - <string name="conn_event_ratchet_sync_started">sutinkama dėl užšifravimo..</string> - <string name="snd_conn_event_ratchet_sync_started">sutinkama su %s šifravimu…</string> - <string name="allow_your_contacts_adding_message_reactions">Leisti mano adresatams reaguoti į pranešimus.</string> - <string name="allow_your_contacts_to_call">Leisti mano adresatams man skambinti.</string> - <string name="allow_message_reactions">Leisti reaguoti į pranešimus.</string> - <string name="v5_0_app_passcode">Programos prieigos kodas</string> - <string name="v5_1_better_messages">Geresni pranešimai</string> - <string name="la_auth_failed">Autentiškumo patvirtinimas nepavyko</string> - <string name="accept_connection_request__question">Ar norite priimti sujungimo užklausą\?</string> + <string name="allow_calls_only_if">Leisti skambučius tik tuo atveju, jeigu juos leidžia jūsų adresatas.</string> + <string name="conn_event_ratchet_sync_started">sutariama dėl šifravimo…</string> + <string name="snd_conn_event_ratchet_sync_started">sutariama dėl %s šifravimo…</string> + <string name="allow_your_contacts_adding_message_reactions">Leisti jūsų adresatams reaguoti į žinutes.</string> + <string name="allow_your_contacts_to_call">Leisti jūsų adresatams jums skambinti.</string> + <string name="allow_message_reactions">Leisti reaguoti į žinutes.</string> + <string name="v5_1_better_messages">Geresnės žinutės</string> + <string name="la_auth_failed">Tapatybės patvirtinimas patyrė nesėkmę</string> + <string name="accept_connection_request__question">Priimti sujungimo užklausą?</string> <string name="one_time_link_short">vienkartinė nuoroda</string> - <string name="accept_call_on_lock_screen">Priimti</string> + <string name="accept_call_on_lock_screen">Atsiliepti</string> <string name="auto_accept_contact">Automatiškai priimti</string> <string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b>Geriau baterijai</b>. Fono paslauga tikrina pranešimus kas 10 minučių. Galite praleisti skambučius arba skubius pranešimus.]]></string> <string name="callstatus_in_progress">vyksta skambutis</string> @@ -666,4 +661,78 @@ <string name="connect_via_member_address_alert_title">Prisijungti tiesiogiai?</string> <string name="create_group_button">Sukurti grupę</string> <string name="dark_theme">Tamsus apipavidalinimas</string> + <string name="feature_off">išjungta</string> + <string name="turn_off_system_restriction_button">Atverti programėlės nustatymus</string> + <string name="chat_preferences_off">išjungta</string>` + <string name="loading_chats">Įkeliami pokalbiai…</string> + <string name="only_group_owners_can_enable_voice">Tik grupės savininkai gali įjungti balso žinutes.</string> + <string name="thousand_abbreviation">tūkst.</string> + <string name="paste_button">Įdėti</string> + <string name="v5_5_message_delivery">Pagerintas žinučių pristatymas</string> + <string name="settings_notification_preview_title">Pranešimo peržiūra</string> + <string name="loading_remote_file_title">Įkeliamas failas</string> + <string name="notifications">Pranešimai</string> + <string name="if_you_received_simplex_invitation_link_you_can_open_in_browser">Jei gavote SimpleX Chat pakvietimo nuorodą, galite ją atverti naršyklėje:</string> + <string name="icon_descr_more_button">Daugiau</string> + <string name="paste_the_link_you_received">Įdėkite gautą nuorodą</string> + <string name="invalid_qr_code">Neteisingas QR kodas</string> + <string name="network_settings_title">Tinklo nustatymai</string> + <string name="rcv_group_event_member_left">išėjo</string> + <string name="v4_5_reduced_battery_usage_descr">Daugiau patobulinimų jau greitai!</string> + <string name="v5_5_join_group_conversation">Prisijunkite prie pokalbių grupėse</string> + <string name="connect_plan_open_group">Atverti grupę</string> + <string name="invite_friends">Pakviesti draugus</string> + <string name="only_you_can_make_calls">Tik jūs galite skambinti.</string> + <string name="v5_0_polish_interface">Naudotojo sąsaja lenkų kalba</string> + <string name="v5_2_more_things_descr">- stabilesnis žinučių pristatymas. +\n- šiek tiek geresnės grupės. +\n- ir daugiau!</string> + <string name="only_your_contact_can_make_calls">Tik jūsų adresatas gali skambinti.</string> + <string name="contact_developers">Atnaujinkite programėlę ir susisiekite su plėtotojais.</string> + <string name="open_verb">Atverti</string> + <string name="chat_item_ttl_none">niekada</string> + <string name="learn_more">Sužinoti daugiau</string> + <string name="opening_database">Atveriama duomenų bazė…</string> + <string name="agent_internal_error_title">Vidinė klaida</string> + <string name="import_theme">Importuoti apipavidalinimą</string> + <string name="only_you_can_send_voice">Tik jūs galite siųsti balso žinutes.</string> + <string name="v4_5_multiple_chat_profiles">Daug pokalbio profilių</string> + <string name="ok">Gerai</string> + <string name="v4_5_private_filenames">Privatūs failų pavadinimai</string> + <string name="info_menu">Informacija</string> + <string name="open_simplex_chat_to_accept_call">Atverkite SimpleX Chat norėdami atsiliepti</string> + <string name="desktop_incompatible_version">Nesuderinama versija</string> + <string name="non_content_uri_alert_title">Neteisingas failo kelias</string> + <string name="settings_notifications_mode_title">Pranešimų tarnyba</string> + <string name="notification_preview_new_message">nauja žinutė</string> + <string name="network_use_onion_hosts_no">Ne</string> + <string name="open_database_folder">Atverti duomenų bazės aplanką</string> + <string name="joining_group">Prisijungiama prie grupės</string> + <string name="group_welcome_preview">Peržiūra</string> + <string name="v4_5_italian_interface">Naudotojo sąsaja italų kalba</string> + <string name="v4_6_reduced_battery_usage_descr">Daugiau patobulinimų jau greitai!</string> + <string name="v5_1_japanese_portuguese_interface">Naudotojo sąsaja japonų ir italų kalbomis</string> + <string name="new_chat">Naujas pokalbis</string> + <string name="display_name__field">Profilio pavadinimas:</string> + <string name="prohibit_message_deletion">Uždrausti negrįžtamai ištrinti žinutes.</string> + <string name="network_and_servers">Tinklas ir serveriai</string> + <string name="group_member_status_left">išėjo</string> + <string name="group_member_role_owner">savininkas</string> + <string name="chat_preferences_no">ne</string> + <string name="prohibit_calls">Uždrausti garso/vaizdo skambučius.</string> + <string name="only_you_can_send_disappearing">Tik jūs galite siųsti išnykstančias žinutes.</string> + <string name="only_your_contact_can_send_disappearing">Tik jūsų adresatas gali siųsti išnykstančias žinutes.</string> + <string name="this_device_version"><![CDATA[<i>(šis įrenginys v%s)</i>]]></string> + <string name="only_one_device_can_work_at_the_same_time">Vienu metu gali veikti tik vienas įrenginys</string> + <string name="not_compatible">Nesuderinama!</string> + <string name="rcv_group_event_open_chat">Atverti</string> + <string name="open_chat">Atverti pokalbį</string> + <string name="network_status">Tinklo būsena</string> + <string name="email_invite_subject">Bendraukime per Simplex Chat</string> + <string name="message_too_large">Žinutė per didelė</string> + <string name="new_in_version">Štai kas naujo %s versijoje</string> + <string name="only_your_contact_can_send_voice">Tik jūsų adresatas gali siųsti balso žinutes.</string> + <string name="only_owners_can_enable_files_and_media">Tik grupės savininkai gali įjungti failus ir mediją.</string> + <string name="only_group_owners_can_change_prefs">Tik grupės savininkai gali keisti grupės nuostatas.</string> + <string name="import_theme_error">Apipavidalinimo importavimo klaida</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml index 7ee6ecdb35..b9f94d9db4 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -440,8 +440,8 @@ <string name="chat_item_ttl_none">nooit</string> <string name="no_received_app_files">Geen ontvangen of verzonden bestanden</string> <string name="alert_title_group_invitation_expired">Uitnodiging verlopen!</string> - <string name="rcv_group_event_member_left">verlaten</string> - <string name="group_member_status_left">verlaten</string> + <string name="rcv_group_event_member_left">is vertrokken</string> + <string name="group_member_status_left">vertrokken</string> <string name="group_member_status_invited">uitgenodigd</string> <string name="button_leave_group">Groep verlaten</string> <string name="info_row_local_name">Lokale naam</string> @@ -523,7 +523,7 @@ <string name="rcv_group_event_invited_via_your_group_link">uitgenodigd via je groep link</string> <string name="incognito">Incognito</string> <string name="icon_descr_call_missed">Gemiste oproep</string> - <string name="description_via_contact_address_link_incognito">incognito via contactadres link</string> + <string name="description_via_contact_address_link_incognito">incognito via contact adres link</string> <string name="description_via_group_link_incognito">incognito via groep link</string> <string name="description_via_one_time_link_incognito">incognito via eenmalige link</string> <string name="invalid_chat">ongeldige gesprek</string> @@ -933,7 +933,7 @@ <string name="trying_to_connect_to_server_to_receive_messages_with_error">Er wordt geprobeerd verbinding te maken met de server die wordt gebruikt om berichten van dit contact te ontvangen (fout: %1$s).</string> <string name="unknown_message_format">onbekend berichtformaat</string> <string name="simplex_link_mode_browser">Via browser</string> - <string name="description_via_contact_address_link">via contactadres link</string> + <string name="description_via_contact_address_link">via contact adres link</string> <string name="description_via_group_link">via groep link</string> <string name="description_via_one_time_link">via een eenmalige link</string> <string name="simplex_link_connection">via %1$s</string> @@ -1498,7 +1498,7 @@ <string name="v5_4_block_group_members">Groepsleden blokkeren</string> <string name="v5_4_incognito_groups_descr">Maak een groep met een willekeurig profiel.</string> <string name="v5_4_link_mobile_desktop">Koppel mobiele en desktop-apps! 🔗</string> - <string name="v5_4_link_mobile_desktop_descr">Via een beveiligd kwantumbestendig protocol.</string> + <string name="v5_4_link_mobile_desktop_descr">Via een beveiligd quantum bestendig protocol.</string> <string name="v5_4_block_group_members_descr">Om ongewenste berichten te verbergen.</string> <string name="v5_4_better_groups">Betere groepen</string> <string name="v5_4_incognito_groups">Incognitogroepen</string> @@ -1529,16 +1529,16 @@ <string name="retry_verb">Opnieuw proberen</string> <string name="camera_not_available">Camera niet beschikbaar</string> <string name="enable_sending_recent_history">Stuur tot 100 laatste berichten naar nieuwe leden.</string> - <string name="add_contact_button_to_create_link_or_connect_via_link"><![CDATA[<b>Contact toevoegen</b>: om een nieuwe uitnodigingslink te maken, of om verbinding te maken via een link die u heeft ontvangen.]]></string> + <string name="add_contact_button_to_create_link_or_connect_via_link"><![CDATA[<b>Contact toevoegen</b>: om een nieuwe uitnodigings link te maken, of om verbinding te maken via een link die u heeft ontvangen.]]></string> <string name="disable_sending_recent_history">Stuur geen geschiedenis naar nieuwe leden.</string> <string name="or_show_this_qr_code">Of laat deze code zien</string> <string name="recent_history_is_sent_to_new_members">Er worden maximaal 100 laatste berichten naar nieuwe leden verzonden.</string> <string name="code_you_scanned_is_not_simplex_link_qr_code">De code die u heeft gescand is geen SimpleX link QR-code.</string> <string name="the_text_you_pasted_is_not_a_link">De tekst die u hebt geplakt is geen SimpleX link.</string> <string name="enable_camera_access">Schakel cameratoegang in</string> - <string name="you_can_view_invitation_link_again">U kunt de uitnodigingslink opnieuw bekijken in de verbindingsdetails.</string> + <string name="you_can_view_invitation_link_again">U kunt de uitnodigings link opnieuw bekijken in de verbindings details.</string> <string name="keep_unused_invitation_question">Ongebruikte uitnodiging bewaren?</string> - <string name="share_this_1_time_link">Deel deze eenmalige uitnodigingslink</string> + <string name="share_this_1_time_link">Deel deze eenmalige uitnodigings link</string> <string name="create_group_button_to_create_new_group"><![CDATA[<b>Groep aanmaken</b>: om een nieuwe groep aan te maken.]]></string> <string name="recent_history">Zichtbare geschiedenis</string> <string name="la_app_passcode">App toegangscode</string> @@ -1551,7 +1551,7 @@ <string name="tap_to_scan">Tik om te scannen</string> <string name="keep_invitation_link">Bewaar</string> <string name="tap_to_paste_link">Tik om de link te plakken</string> - <string name="search_or_paste_simplex_link">Zoek of plak een SimpleX link</string> + <string name="search_or_paste_simplex_link">Zoeken of plak een SimpleX link</string> <string name="chat_is_stopped_you_should_transfer_database">De chat is gestopt. Als u deze database al op een ander apparaat heeft gebruikt, moet u deze terugzetten voordat u met chatten begint.</string> <string name="start_chat_question">Begin chat?</string> <string name="show_internal_errors">Toon interne fouten</string> @@ -1606,7 +1606,7 @@ <string name="profile_update_event_contact_name_changed">contactpersoon %1$s gewijzigd in %2$s</string> <string name="error_deleting_note_folder">Fout bij verwijderen van privénotities</string> <string name="clear_note_folder_question">Privénotities verwijderen?</string> - <string name="v5_5_simpler_connect_ui_descr">Zoekbalk accepteert uitnodigingslinks.</string> + <string name="v5_5_simpler_connect_ui_descr">Zoekbalk accepteert uitnodigings links.</string> <string name="v5_5_private_notes_descr">‐Met versleutelde bestanden en media.</string> <string name="v5_5_message_delivery_descr">Met verminderd batterijgebruik.</string> <string name="saved_message_title">Opgeslagen bericht</string> @@ -1628,4 +1628,89 @@ <string name="unblock_for_all_question">Lid voor iedereen deblokkeren?</string> <string name="snd_group_event_member_blocked">je hebt %s geblokkeerd</string> <string name="snd_group_event_member_unblocked">je hebt %s gedeblokkeerd</string> + <string name="message_too_large">Bericht te groot</string> + <string name="welcome_message_is_too_long">Welkomstbericht is te lang</string> + <string name="database_migration_in_progress">De databasemigratie wordt uitgevoerd. +\nDit kan enkele minuten duren.</string> + <string name="call_service_notification_audio_call">Audio oproep</string> + <string name="call_service_notification_end_call">Ophangen</string> + <string name="call_service_notification_video_call">Video oproep</string> + <string name="unable_to_open_browser_title">Fout bij het openen van de browser</string> + <string name="unable_to_open_browser_desc">Voor oproepen is de standaard webbrowser vereist. Configureer de standaard browser in het systeem en deel meer informatie met de ontwikkelaars.</string> + <string name="migrate_to_device_confirm_network_settings">Bevestig netwerk instellingen</string> + <string name="migrate_from_device_archive_and_upload">Archiveren en uploaden</string> + <string name="migrate_from_device_archiving_database">Database archiveren</string> + <string name="migrate_from_device_all_data_will_be_uploaded">Al uw contacten, gesprekken en bestanden worden veilig gecodeerd en in delen geüpload naar geconfigureerde XFTP-relays.</string> + <string name="migrate_to_device_apply_onion">Toepassen</string> + <string name="migrate_from_device_using_on_two_device_breaks_encryption"><![CDATA[<b>Let op</b>: als u dezelfde database op twee apparaten gebruikt, wordt de decodering van berichten van uw verbindingen verbroken, als veiligheidsmaatregel.]]></string> + <string name="migrate_from_device_archive_will_be_deleted"><![CDATA[<b>Waarschuwing</b>: het archief wordt verwijderd.]]></string> + <string name="migrate_from_device_cancel_migration">Migratie annuleren</string> + <string name="migrate_to_device_chat_migrated">Chat gemigreerd!</string> + <string name="migrate_from_device_check_connection_and_try_again">Controleer uw internetverbinding en probeer het opnieuw</string> + <string name="migrate_from_device_choose_migrate_from_another_device"><![CDATA[Kies <i>Migreren vanaf een ander apparaat</i> op het nieuwe apparaat en scan de QR-code.]]></string> + <string name="v5_6_safer_groups_descr">Beheerders kunnen een lid voor iedereen blokkeren.</string> + <string name="v5_6_app_data_migration">Migratie van app-gegevens</string> + <string name="migrate_from_device_creating_archive_link">Archief link maken</string> + <string name="migrate_from_device_delete_database_from_device">Verwijder de database van dit apparaat</string> + <string name="migrate_from_device_confirm_you_remember_passphrase">Bevestig dat u het wachtwoord voor de database onthoudt om deze te migreren.</string> + <string name="v5_6_quantum_resistant_encryption_descr">Activeer in directe chats (BETA)!</string> + <string name="migrate_to_device_downloading_details">Link gegevens downloaden</string> + <string name="migrate_from_device_error_saving_settings">Fout bij opslaan van instellingen</string> + <string name="migrate_from_device_error_deleting_database">Fout bij verwijderen database</string> + <string name="migrate_from_device_confirm_upload">Bevestig het uploaden</string> + <string name="migrate_to_device_download_failed">Download mislukt</string> + <string name="migrate_to_device_downloading_archive">Archief downloaden</string> + <string name="migrate_to_device_enter_passphrase">Voer het wachtwoord in</string> + <string name="migrate_to_device_error_downloading_archive">Fout bij het downloaden van het archief</string> + <string name="migrate_from_device_error_exporting_archive">Fout bij het exporteren van de chat database</string> + <string name="migrate_from_device_error_uploading_archive">Fout bij het uploaden van het archief</string> + <string name="e2ee_info_no_pq_short">Deze chat is beveiligd met end-to-end codering.</string> + <string name="conn_event_disabled_pq">standaard end-to-end encryptie</string> + <string name="migrate_to_device_finalize_migration">Voltooi de migratie op een ander apparaat.</string> + <string name="migrate_from_device_chat_should_be_stopped">Om verder te kunnen gaan, moet de chat worden gestopt.</string> + <string name="migrate_from_device_repeat_upload">Herhaal het uploaden</string> + <string name="migrate_from_device_upload_failed">Upload mislukt</string> + <string name="migrate_from_device_try_again">Je kunt het nog een keer proberen.</string> + <string name="migrate_from_device_finalize_migration">Voltooi de migratie</string> + <string name="migrate_from_device_start_chat">Chat starten</string> + <string name="migrate_from_device_error_verifying_passphrase">Fout bij het verifiëren van het wachtwoord:</string> + <string name="v5_6_quantum_resistant_encryption">quantum bestendige encryptie</string> + <string name="v5_6_picture_in_picture_calls">Beeld-in-beeld oproepen</string> + <string name="v5_6_safer_groups">Veiligere groepen</string> + <string name="v5_6_picture_in_picture_calls_descr">Gebruik de app tijdens het gesprek.</string> + <string name="migrate_to_device_migrating">Migreren</string> + <string name="or_paste_archive_link">Of plak de archief link</string> + <string name="migrate_to_device_database_init">Downloaden voorbereiden</string> + <string name="migrate_to_device_repeat_download">Herhaal het downloaden</string> + <string name="migrate_to_device_try_again">Je kunt het nog een keer proberen.</string> + <string name="migrate_to_device_importing_archive">Archief importeren</string> + <string name="migrate_to_device_repeat_import">Herhaal import</string> + <string name="migrate_to_device_file_delete_or_link_invalid">Bestand is verwijderd of de link is ongeldig</string> + <string name="migrate_from_device_exported_file_doesnt_exist">Geëxporteerd bestand bestaat niet</string> + <string name="migrate_from_device_title">Apparaat migreren</string> + <string name="migrate_from_device_to_another_device">Migreer naar een ander apparaat</string> + <string name="migrate_from_device_stopping_chat">Chat stoppen</string> + <string name="migrate_from_device_bytes_uploaded">%s geüpload</string> + <string name="migrate_from_device_uploading_archive">Archief uploaden</string> + <string name="migrate_from_device_you_must_not_start_database_on_two_device"><![CDATA[U <b>mag niet</b> dezelfde database op twee apparaten gebruiken.]]></string> + <string name="migrate_from_device_verify_passphrase">Controleer het wachtwoord</string> + <string name="migrate_from_device_verify_database_passphrase">Controleer het wachtwoord van de database</string> + <string name="migrate_from_device_starting_chat_on_multiple_devices_unsupported">Waarschuwing: het starten van de chat op meerdere apparaten wordt niet ondersteund en zal leiden tot mislukte bezorging van berichten</string> + <string name="migrate_to_device_import_failed">Importeren is mislukt</string> + <string name="invalid_file_link">Ongeldige link</string> + <string name="e2ee_info_no_pq"><![CDATA[Berichten, bestanden en oproepen worden beschermd door <b>end-to-end-codering</b> met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel.]]></string> + <string name="e2ee_info_pq"><![CDATA[Berichten, bestanden en oproepen worden beschermd door <b>quantum bestendige e2e-encryptie</b> met perfecte voorwaartse geheimhouding, afwijzing en inbraakherstel.]]></string> + <string name="migrate_from_another_device">Migreer vanaf een ander apparaat</string> + <string name="migrate_to_device_title">Migreer hierheen</string> + <string name="v5_6_app_data_migration_descr">Migreer naar een ander apparaat via QR-code.</string> + <string name="migrate_from_device_migration_complete">Migratie voltooid</string> + <string name="auth_open_migration_to_another_device">Open het migratiescherm</string> + <string name="migrate_from_device_or_share_this_file_link">Of deel deze bestands link veilig</string> + <string name="paste_archive_link">Archief link plakken</string> + <string name="migrate_to_device_confirm_network_settings_footer">Controleer of de netwerk instellingen correct zijn voor dit apparaat.</string> + <string name="migrate_from_device_database_init">Uploaden voorbereiden</string> + <string name="conn_event_enabled_pq">quantum bestendige e2e-codering</string> + <string name="migrate_to_device_bytes_downloaded">%s gedownload</string> + <string name="set_passphrase">Wachtwoord instellen</string> + <string name="e2ee_info_pq_short">Deze chat wordt beschermd door quantum bestendige end-to-end codering.</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml index 48607217ce..04e40e73cb 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -1630,4 +1630,89 @@ <string name="remote_host_error_timeout"><![CDATA[Osiągnięto limit czasu podczas łączenia z telefonem <b>%s</b>]]></string> <string name="remote_ctrl_error_inactive">Komputer jest nieaktywny</string> <string name="remote_host_error_disconnected"><![CDATA[Telefon <b>%s</b> został rozłączony]]></string> + <string name="welcome_message_is_too_long">Wiadomość powitalna jest zbyt długa</string> + <string name="database_migration_in_progress">Proces migracji bazy danych jest w toku. +\nMoże to potrwać kilka minut.</string> + <string name="message_too_large">Wiadomość jest zbyt duża</string> + <string name="call_service_notification_audio_call">Połączenie audio</string> + <string name="call_service_notification_end_call">Zakończ połączenie</string> + <string name="call_service_notification_video_call">Połączenie wideo</string> + <string name="unable_to_open_browser_title">Błąd podczas otwierania przeglądarki</string> + <string name="unable_to_open_browser_desc">Do połączeń wymagana jest domyślna przeglądarka. Proszę skonfigurować domyślną przeglądarkę systemową, i podzielić się informacją z twórcami.</string> + <string name="e2ee_info_pq_short">Ten czat jest chroniony przez kwantowo odporne szyfrowanie end-to-end.</string> + <string name="e2ee_info_no_pq"><![CDATA[Wiadomości, pliki i połączenia są chronione przez <b>szyfrowanie end-to-end</b> z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu.]]></string> + <string name="auth_open_migration_to_another_device">Otwórz ekran migrowania</string> + <string name="migrate_from_another_device">Zmigruj z innego urządzenia</string> + <string name="set_passphrase">Ustaw hasło</string> + <string name="conn_event_disabled_pq">standardowe szyfrowanie end-to-end</string> + <string name="migrate_to_device_chat_migrated">Czat zmigrowany!</string> + <string name="v5_6_app_data_migration">Migracja danych aplikacji</string> + <string name="v5_6_safer_groups">Bezpieczniejsze grupy</string> + <string name="v5_6_picture_in_picture_calls_descr">Używaj aplikacji podczas połączenia.</string> + <string name="v5_6_picture_in_picture_calls">Połączenia obraz-w-obrazie</string> + <string name="v5_6_safer_groups_descr">Administratorzy mogą blokować członka dla wszystkich.</string> + <string name="invalid_file_link">Nieprawidłowy link</string> + <string name="migrate_to_device_title">Zmigruj tutaj</string> + <string name="migrate_to_device_migrating">Migrowanie</string> + <string name="or_paste_archive_link">Lub wklej link archiwum</string> + <string name="paste_archive_link">Wklej link archiwum</string> + <string name="migrate_to_device_database_init">Przygotowywanie pobrania</string> + <string name="migrate_to_device_bytes_downloaded">%s pobrane</string> + <string name="migrate_to_device_try_again">Możesz spróbować ponownie.</string> + <string name="migrate_to_device_importing_archive">Importowanie archiwum</string> + <string name="migrate_to_device_repeat_download">Powtórz pobieranie</string> + <string name="migrate_to_device_import_failed">Import nie udał się</string> + <string name="migrate_to_device_repeat_import">Powtórz importowanie</string> + <string name="migrate_to_device_error_downloading_archive">Błąd pobierania archiwum</string> + <string name="migrate_to_device_finalize_migration">Dokończ migrację na innym urządzeniu.</string> + <string name="migrate_to_device_apply_onion">Zastosuj</string> + <string name="migrate_from_device_title">Zmigruj urządzenie</string> + <string name="migrate_from_device_to_another_device">Zmigruj do innego urządzenia</string> + <string name="migrate_from_device_database_init">Przygotowywanie wgrania</string> + <string name="migrate_from_device_error_deleting_database">Błąd usuwania bazy danych</string> + <string name="migrate_from_device_stopping_chat">Zatrzymywanie czatu</string> + <string name="migrate_from_device_chat_should_be_stopped">Aby konturować, czat musi zostać zatrzymany.</string> + <string name="migrate_from_device_archiving_database">Archiwizowanie bazy danych</string> + <string name="migrate_from_device_confirm_upload">Potwierdź wgranie</string> + <string name="migrate_from_device_bytes_uploaded">%s wgrane</string> + <string name="migrate_from_device_upload_failed">Wgrywanie nie udane</string> + <string name="migrate_from_device_uploading_archive">Wgrywanie archiwum</string> + <string name="migrate_from_device_repeat_upload">Powtórz wgrywanie</string> + <string name="migrate_from_device_creating_archive_link">Tworzenie linku archiwum</string> + <string name="migrate_from_device_try_again">Możesz spróbować ponownie.</string> + <string name="migrate_from_device_or_share_this_file_link">Lub bezpiecznie udostępnij ten link pliku</string> + <string name="migrate_from_device_start_chat">Rozpocznij czat</string> + <string name="migrate_from_device_migration_complete">Migracja zakończona</string> + <string name="migrate_from_device_confirm_you_remember_passphrase">Potwierdź, że pamiętasz hasło do bazy danych, aby ją zmigrować.</string> + <string name="migrate_from_device_verify_database_passphrase">Zweryfikuj hasło bazy danych</string> + <string name="migrate_from_device_check_connection_and_try_again">Sprawdź swoje połączenie z internetem i spróbuj ponownie</string> + <string name="migrate_from_device_all_data_will_be_uploaded">Wszystkie twoje kontakty, konwersacje i pliki będą bezpiecznie szyfrowane i wgrywane w kawałkach do skonfigurowanych przekaźników XFTP.</string> + <string name="migrate_from_device_archive_and_upload">Archiwizuj i prześlij</string> + <string name="migrate_from_device_using_on_two_device_breaks_encryption"><![CDATA[<b>Uwaga</b>: używanie tej samej bazy danych na dwóch urządzeniach zepsuje odszyfrowywanie wiadomości twoich połączeń, jako zabezpieczenie.]]></string> + <string name="migrate_from_device_archive_will_be_deleted"><![CDATA[<b>Ostrzeżenie</b>: archiwum zostanie usunięte.]]></string> + <string name="migrate_from_device_cancel_migration">Anuluj migrację</string> + <string name="migrate_from_device_choose_migrate_from_another_device"><![CDATA[Wybierz <i>Zmigruj z innego urządzenia</i> na nowym urządzeniu i zeskanuj kod QR.]]></string> + <string name="migrate_to_device_confirm_network_settings">Potwierdź ustawienia sieciowe</string> + <string name="migrate_from_device_delete_database_from_device">Usuń bazę danych z tego urządzenia</string> + <string name="migrate_to_device_download_failed">Pobieranie nie udane</string> + <string name="migrate_to_device_downloading_archive">Pobieranie archiwum</string> + <string name="migrate_to_device_downloading_details">Pobieranie szczegółów linku</string> + <string name="v5_6_quantum_resistant_encryption_descr">Włącz w czatach bezpośrednich (BETA)!</string> + <string name="migrate_to_device_enter_passphrase">Wprowadź hasło</string> + <string name="migrate_from_device_error_exporting_archive">Błąd eksportu bazy danych czatu</string> + <string name="migrate_from_device_error_saving_settings">Błąd zapisywania ustawień</string> + <string name="migrate_from_device_error_uploading_archive">Błąd wgrywania archiwum</string> + <string name="migrate_from_device_error_verifying_passphrase">Błąd weryfikowania hasła:</string> + <string name="migrate_from_device_exported_file_doesnt_exist">Wyeksportowany plik nie istnieje</string> + <string name="e2ee_info_no_pq_short">Ten czat jest chroniony przez szyfrowanie end-to-end.</string> + <string name="migrate_from_device_verify_passphrase">Zweryfikuj hasło</string> + <string name="migrate_from_device_starting_chat_on_multiple_devices_unsupported">Ostrzeżenie: rozpoczęcie czatu na wielu urządzeniach nie jest wspierane i spowoduje niepowodzenia dostarczania wiadomości</string> + <string name="migrate_from_device_you_must_not_start_database_on_two_device"><![CDATA[<b>Nie możesz</b> używać tej samej bazy na dwóch urządzeniach.]]></string> + <string name="migrate_to_device_file_delete_or_link_invalid">Plik został usunięty lub łącze jest nieprawidłowe</string> + <string name="migrate_from_device_finalize_migration">Dokończ migrację</string> + <string name="e2ee_info_pq"><![CDATA[Wiadomości, pliki i połączenia są chronione przez <b>kwantowo odporne szyfrowanie end-to-end</b> z doskonałym utajnianiem z wyprzedzeniem i odzyskiem po złamaniu.]]></string> + <string name="v5_6_app_data_migration_descr">Zmigruj do innego urządzenia przez kod QR.</string> + <string name="migrate_to_device_confirm_network_settings_footer">Proszę potwierdzić, że ustawienia sieciowe są prawidłowe dla tego urządzenia.</string> + <string name="conn_event_enabled_pq">kwantowo odporne szyfrowanie e2e</string> + <string name="v5_6_quantum_resistant_encryption">Kwantowo odporne szyfrowanie</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml index bf7d29c9c3..cc0bd49454 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt-rBR/strings.xml @@ -10,9 +10,9 @@ <string name="rcv_conn_event_switch_queue_phase_changing">mudando endereço…</string> <string name="snd_conn_event_switch_queue_phase_changing_for_member">mudando endereço para %s…</string> <string name="snd_conn_event_switch_queue_phase_changing">mudando endereço…</string> - <string name="change_role">Mudar função</string> + <string name="change_role">Mudar cargo</string> <string name="change_verb">Mudar</string> - <string name="both_you_and_your_contacts_can_delete">Você e seu contato podem excluir mensagens enviadas de forma irreversível.</string> + <string name="both_you_and_your_contacts_can_delete">Você e seu contato podem excluir mensagens enviadas de forma irreversível. (24 horas)</string> <string name="it_can_disabled_via_settings_notifications_still_shown"><![CDATA[<b>Pode ser desativado nas configurações</b> – as notificações ainda serão exibidas enquanto o aplicativo estiver em execução.]]></string> <string name="both_you_and_your_contact_can_send_voice">Você e seu contato podem enviar mensagens de voz.</string> <string name="notifications_mode_off_desc">O aplicativo pode receber notificações apenas quando estiver em execução, nenhum serviço em segundo plano será iniciado</string> @@ -25,61 +25,61 @@ <string name="back">Voltar</string> <string name="choose_file">Arquivo</string> <string name="accept_contact_button">Aceitar</string> - <string name="clear_chat_question">Limpar chat\?</string> + <string name="clear_chat_question">Limpar bate-papo?</string> <string name="clear_verb">Limpar</string> - <string name="clear_chat_button">Limpar chat</string> + <string name="clear_chat_button">Limpar bate-papo</string> <string name="clear_chat_menu_action">Limpar</string> <string name="icon_descr_cancel_link_preview">cancelar pré-visualização do link</string> <string name="feature_cancelled_item">cancelado %s</string> - <string name="app_version_name">Versão do App: v%s</string> + <string name="app_version_name">Versão do Aplicativo: v%s</string> <string name="callstatus_calling">chamando…</string> <string name="callstatus_in_progress">chamada em andamento</string> <string name="accept">Aceitar</string> <string name="call_already_ended">Chamada já encerrada!</string> <string name="icon_descr_call_progress">Chamada em andamento</string> <string name="icon_descr_call_ended">Chamada encerrada</string> - <string name="answer_call">Atender ligação</string> - <string name="integrity_msg_bad_hash">hash de mensagem incorreto</string> + <string name="answer_call">Atender chamada</string> + <string name="integrity_msg_bad_hash">hash de mensagem incorreta</string> <string name="integrity_msg_bad_id">ID de mensagem incorreta</string> - <string name="impossible_to_recover_passphrase"><![CDATA[<b>Observação</b>: você NÃO poderá recuperar ou alterar a senha se a perder.]]></string> + <string name="impossible_to_recover_passphrase"><![CDATA[<b>Atenção</b>: você NÃO poderá recuperar ou alterar a senha se a perder.]]></string> <string name="cannot_receive_file">Não é possível receber o arquivo</string> <string name="icon_descr_cancel_image_preview">Cancelar visualização da imagem</string> - <string name="icon_descr_close_button">Botão Fechar</string> + <string name="icon_descr_close_button">Botão de fechar</string> <string name="clear_verification">Limpar verificação</string> - <string name="app_version_title">Versão do App</string> + <string name="app_version_title">Versão do Aplicativo</string> <string name="bold_text">negrito</string> <string name="callstatus_error">erro de chamada</string> <string name="settings_audio_video_calls">Chamadas de áudio e vídeo</string> <string name="accept_call_on_lock_screen">Aceitar</string> <string name="call_on_lock_screen">Chamadas na tela de bloqueio:</string> <string name="icon_descr_audio_on">Áudio ligado</string> - <string name="chat_database_imported">Banco de dados de chat importado</string> + <string name="chat_database_imported">Banco de dados do bate-papo importado</string> <string name="keychain_is_storing_securely">Android Keystore é usada para armazenar a senha com segurança - permite que o serviço de notificação funcione.</string> <string name="keychain_allows_to_receive_ntfs">A Android Keystore será usada para armazenar a senha com segurança depois que você reiniciar o aplicativo ou alterar a senha - isso permitirá o recebimento de notificações.</string> <string name="cannot_access_keychain">Não é possível acessar a Keystore para salvar a senha do banco de dados</string> - <string name="chat_archive_section">ARQUIVO DE CHAT</string> - <string name="chat_is_stopped_indication">O chat está parado</string> + <string name="chat_archive_section">ARQUIVO DE BATE-PAPO</string> + <string name="chat_is_stopped_indication">O bate-papo está parado</string> <string name="clear_contacts_selection_button">Limpar</string> - <string name="chat_preferences">Preferências de chat</string> - <string name="network_session_mode_user">perfil de chat</string> + <string name="chat_preferences">Preferências de bate-papo</string> + <string name="network_session_mode_user">Perfil de bate-papo</string> <string name="icon_descr_audio_off">Áudio desligado</string> <string name="auto_accept_images">Aceitar imagens automaticamente</string> - <string name="chat_database_deleted">Banco de dados de chat excluído</string> + <string name="chat_database_deleted">Banco de dados do bate-papo excluído</string> <string name="invite_prohibited">Não é possível convidar o contato!</string> <string name="turning_off_service_and_periodic">A otimização da bateria está ativa, desligando o serviço em segundo plano e as solicitações periódicas de novas mensagens. Você pode reativá-los através das configurações.</string> <string name="database_initialization_error_title">Não é possível inicializar o banco de dados</string> <string name="attach">Anexar</string> <string name="cancel_verb">Cancelar</string> - <string name="chat_console">Console de chat</string> + <string name="chat_console">Console de bate-papo</string> <string name="smp_servers_check_address">Verifique o endereço do servidor e tente novamente.</string> - <string name="network_session_mode_user_description"><![CDATA[Uma conexão TCP separada (e credencial SOCKS) será usada <b>para cada perfil de chat que você tiver no aplicativo</b>.]]></string> - <string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>Melhor para bateria</b>. Você receberá notificações apenas quando o aplicativo estiver em execução (sem segundo plano).]]></string> + <string name="network_session_mode_user_description"><![CDATA[Uma conexão TCP separada (e credencial SOCKS) será usada <b>para cada perfil de bate-papo que você tiver no aplicativo</b>.]]></string> + <string name="onboarding_notifications_mode_off_desc"><![CDATA[<b>Melhor para bateria</b>. Você receberá notificações apenas quando o aplicativo estiver em execução (SEM o serviço em segundo plano).]]></string> <string name="onboarding_notifications_mode_service_desc"><![CDATA[<b>Consome mais bateria</b>! O serviço em segundo plano está sempre em execução - as notificações são exibidas assim que as mensagens estiverem disponíveis.]]></string> - <string name="settings_section_title_chats">CHATS</string> - <string name="settings_section_title_icon">ÍCONE DO APP</string> - <string name="chat_database_section">BANCO DE DADOS DE CHAT</string> - <string name="chat_is_running">O chat está em execução</string> - <string name="chat_is_stopped">O chat está parado</string> + <string name="settings_section_title_chats">BATE-PAPOS</string> + <string name="settings_section_title_icon">ÍCONE DO APLICATIVO</string> + <string name="chat_database_section">BANCO DE DADOS DE BATE-PAPO</string> + <string name="chat_is_running">O bate-papo está em execução</string> + <string name="chat_is_stopped">O bate-papo está parado</string> <string name="change_database_passphrase_question">Alterar senha do banco de dados\?</string> <string name="chat_archive_header">Arquivo de chat</string> <string name="rcv_conn_event_switch_queue_phase_completed">endereço alterado para você</string> @@ -89,17 +89,17 @@ <string name="v4_2_auto_accept_contact_requests">Aceitar solicitações de contato automaticamente</string> <string name="appearance_settings">Aparência</string> <string name="notifications_mode_service_desc">O serviço em segundo plano está sempre em execução - as notificações serão exibidas assim que as mensagens estiverem disponíveis.</string> - <string name="network_session_mode_entity_description">Uma conexão TCP separada (e credencial SOCKS) será usada <b>para cada contato e membro do grupo</b>. -\n<b>Observação</b>: se você tiver muitas conexões, o consumo de bateria e tráfego pode ser substancialmente maior e algumas conexões podem falhar.</string> + <string name="network_session_mode_entity_description">Uma conexão TCP separada (e credencial SOCKS) será usada <b>para cada contato e membro do grupo</b>. +\n<b>Atenção</b>: se você tiver muitas conexões, o consumo de bateria e tráfego pode ser substancialmente maior e algumas conexões podem falhar.</string> <string name="onboarding_notifications_mode_periodic_desc"><![CDATA[<b>Bom para bateria</b>. O serviço em segundo plano procura por mensagens a cada 10 minutos. Você pode perder chamadas ou mensagens urgentes.]]></string> <string name="callstatus_ended">chamda encerrada %1$s</string> - <string name="chat_with_developers">Chat com os desenvolvedores</string> + <string name="chat_with_developers">Converse com os desenvolvedores</string> <string name="create_group_link">Criar link de grupo</string> <string name="button_create_group_link">Criar link</string> <string name="create_secret_group_title">Criar grupo secreto</string> <string name="theme_dark">Escuro</string> - <string name="connect_via_invitation_link">Conectar via link de convite\?</string> - <string name="connect_via_contact_link">Conectar via link de contato\?</string> + <string name="connect_via_invitation_link">Conectar via link de convite único?</string> + <string name="connect_via_contact_link">Conectar via endereço do contato?</string> <string name="smp_server_test_create_queue">Criar fila</string> <string name="notification_preview_mode_contact">Nome de contato</string> <string name="notification_preview_somebody">Contato oculto:</string> @@ -107,7 +107,7 @@ <string name="allow_verb">Permitir</string> <string name="allow_to_send_disappearing">Permitir enviar mensagens temporárias.</string> <string name="allow_direct_messages">Permitir o envio de mensagens diretas aos membros.</string> - <string name="connect_via_link_or_qr">Conectar via link/QR</string> + <string name="connect_via_link_or_qr">Conectar via link/QR code</string> <string name="clear_chat_warning">Todas as mensagens serão excluídas - isso não pode ser desfeito! As mensagens serão excluídas APENAS para você.</string> <string name="smp_servers_preset_add">Adicionar servidores pré-definidos</string> <string name="smp_servers_add">Adicionar servidor…</string> @@ -123,7 +123,7 @@ <string name="status_contact_has_e2e_encryption">Contato tem criptografia e2e</string> <string name="status_contact_has_no_e2e_encryption">contato não tem criptografia e2e</string> <string name="contact_preferences">Preferências de contato</string> - <string name="allow_to_delete_messages">Permite excluir irreversivelmente as mensagens enviadas.</string> + <string name="allow_to_delete_messages">Permite excluir irreversivelmente as mensagens enviadas. (24 horas)</string> <string name="chat_preferences_always">sempre</string> <string name="v4_3_improved_server_configuration_desc">Adicione servidores escaneando o QR code.</string> <string name="allow_to_send_voice">Permitir enviar mensagens de voz.</string> @@ -137,21 +137,21 @@ <string name="users_delete_all_chats_deleted">Todos os chats e mensagens serão excluídos - isso não pode ser desfeito!</string> <string name="accept_feature">Aceitar</string> <string name="allow_disappearing_messages_only_if">Permitir mensagens temporárias apenas se o seu contato permitir.</string> - <string name="allow_irreversible_message_deletion_only_if">Permita a exclusão irreversível da mensagem somente se o seu contato permitir.</string> + <string name="allow_irreversible_message_deletion_only_if">Permita a exclusão irreversível da mensagem somente se o seu contato permitir. (24 horas)</string> <string name="allow_your_contacts_to_send_disappearing_messages">Permitir que seus contatos enviem mensagens temporárias.</string> <string name="allow_voice_messages_only_if">Permitir mensagens de voz somente se o seu contato permitir.</string> <string name="allow_your_contacts_to_send_voice_messages">Permitir que seus contatos enviem mensagens de voz.</string> <string name="group_member_role_admin">administrador</string> <string name="all_group_members_will_remain_connected">Todos os membros do grupo permanecerão conectados.</string> <string name="contacts_can_mark_messages_for_deletion">"Contatos podem marcar mensagens para exclusão; você será capaz de visualizá-los."</string> - <string name="connect_via_group_link">Conectar via link do grupo\?</string> + <string name="connect_via_group_link">Se conectar ao grupo?</string> <string name="contact_already_exists">Contato já existe</string> <string name="icon_descr_contact_checked">Contato verificado</string> <string name="alert_title_contact_connection_pending">Contato ainda não está conectado!</string> <string name="contribute">Contribuir</string> <string name="create_profile_button">Criar</string> <string name="network_enable_socks_info">Acessar os servidores via proxy SOCKS na porta %d\? O proxy deve ser iniciado antes de habilitar esta opção.</string> - <string name="allow_your_contacts_irreversibly_delete">Permitir que seus contatos excluam de forma irreversível as mensagens enviadas.</string> + <string name="allow_your_contacts_irreversibly_delete">Permitir que seus contatos excluam de forma irreversível as mensagens enviadas. (24 horas)</string> <string name="smp_servers_add_to_another_device">Adicionar a outro dispositivo</string> <string name="v4_2_group_links_desc">Os administradores podem criar os links para ingressar em grupos.</string> <string name="allow_voice_messages_question">Permitir mensagens de voz\?</string> @@ -168,7 +168,7 @@ <string name="connect_button">Conectar</string> <string name="callstatus_connecting">conectando chamada…</string> <string name="delete_chat_profile_question">Excluir perfil de chat\?</string> - <string name="delete_files_and_media_for_all_users">Excluir arquivos de todos os perfis de chat</string> + <string name="delete_files_and_media_for_all_users">Excluir arquivos de todos os perfis de bate-papo</string> <string name="display_name_connecting">conectando…</string> <string name="connection_error">Erro de conexão</string> <string name="button_delete_contact">Excluir contato</string> @@ -182,7 +182,7 @@ <string name="database_passphrase_is_required">Senha do banco de dados é necessária para abrir o chat.</string> <string name="delete_archive">Excluir arquivo</string> <string name="delete_chat_archive_question">Excluir arquivo de chat\?</string> - <string name="rcv_group_event_changed_member_role">função alterada de %s para %s</string> + <string name="rcv_group_event_changed_member_role">cargo alterado de %s para %s</string> <string name="rcv_group_event_member_connected">conectado</string> <string name="delete_link">Excluir link</string> <string name="delete_link_question">Excluir link\?</string> @@ -193,7 +193,7 @@ <string name="for_me_only">Excluir para mim</string> <string name="group_connection_pending">conectando…</string> <string name="delete_contact_question">Excluir contato\?</string> - <string name="confirm_verb">confirmar</string> + <string name="confirm_verb">Confirmar</string> <string name="database_passphrase_and_export">Senha e exportação do banco de dados</string> <string name="icon_descr_call_connecting">Conectando chamada</string> <string name="delete_messages">Excluir mensagens</string> @@ -207,9 +207,9 @@ <string name="notification_contact_connected">Conectado</string> <string name="icon_descr_server_status_connected">Conectado</string> <string name="audio_call_no_encryption">chamada de áudio (não criptografada ponta-a-ponta)</string> - <string name="change_member_role_question">Alterar a função do grupo\?</string> + <string name="change_member_role_question">Alterar a cargo do grupo?</string> <string name="icon_descr_audio_call">chamada de áudio</string> - <string name="rcv_group_event_changed_your_role">mudou sua função para %s</string> + <string name="rcv_group_event_changed_your_role">mudou sua cargo para %s</string> <string name="v4_4_verify_connection_security_desc">Compare os códigos de segurança com seus contatos.</string> <string name="auth_confirm_credential">Confirme sua credencial</string> <string name="callstate_connecting">conectando…</string> @@ -242,10 +242,10 @@ <string name="group_member_status_connected">conectado</string> <string name="group_member_status_accepted">conectando (aceito)</string> <string name="ttl_d">%dd</string> - <string name="v4_5_transport_isolation_descr">Por perfil de chat (padrão) ou por conexão (BETA).</string> + <string name="v4_5_transport_isolation_descr">Por perfil de bate-papo (padrão) ou por conexão (BETA).</string> <string name="accept_contact_incognito_button">Aceitar anônimo</string> <string name="delete_messages_after">Excluir mensagens após</string> - <string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 desktop: Scaneie o código QR exibido no aplicativo, via <b>Scan QR code</b>]]></string> + <string name="desktop_scan_QR_code_from_app_via_scan_QR_code"><![CDATA[💻 desktop: Escaneie o QR code exibido no aplicativo, via <b>Escanear QR code</b>]]></string> <string name="delete_pending_connection__question">Excluir conexão pendente\?</string> <string name="simplex_link_mode_description">Descrição</string> <string name="smp_servers_delete_server">Excluir servidor</string> @@ -254,14 +254,14 @@ <string name="delete_member_message__question">Excluir mensagem do membro\?</string> <string name="smp_server_test_delete_queue">Excluir fila</string> <string name="settings_section_title_device">DISPOSITIVO</string> - <string name="settings_developer_tools">Ferramentas de desenvolvimento</string> + <string name="settings_developer_tools">Ferramentas de desenvolvedor</string> <string name="group_member_status_introduced">conectando (introduzido)</string> - <string name="color_primary">Acento</string> + <string name="color_primary">Realçe</string> <string name="error_removing_member">Erro ao remover membro</string> - <string name="error_changing_role">Erro ao alterar função</string> + <string name="error_changing_role">Erro ao alterar cargo</string> <string name="conn_level_desc_direct">direto</string> <string name="server_error">erro</string> - <string name="failed_to_parse_chat_title">Falha ao carregar o chat</string> + <string name="failed_to_parse_chat_title">Falha ao carregar o bate-papo</string> <string name="error_setting_network_config">Erro ao atualizar a configuração de conexão</string> <string name="error_sending_message">Erro ao enviar mensagem</string> <string name="error_adding_members">Erro ao adicionar membro(s)</string> @@ -274,10 +274,10 @@ <string name="error_receiving_file">Erro ao receber arquivo</string> <string name="error_creating_address">Erro ao criar endereço</string> <string name="display_name__field">Nome de exibição:</string> - <string name="error_starting_chat">Erro ao iniciar o chat</string> + <string name="error_starting_chat">Erro ao iniciar o bate-papo</string> <string name="error_deleting_database">Erro ao excluir banco de dados de chat</string> <string name="encrypt_database">Criptografar</string> - <string name="network_option_enable_tcp_keep_alive">Ativar TCP keep-alive</string> + <string name="network_option_enable_tcp_keep_alive">Ativar TCP manter-vivo</string> <string name="failed_to_create_user_title">Erro ao criar perfil!</string> <string name="error_joining_group">Erro ao ingressar no grupo</string> <string name="failed_to_create_user_duplicate_title">Nome de exibição duplicado!</string> @@ -291,22 +291,22 @@ <string name="error_accepting_contact_request">Erro ao aceitar solicitação de contato</string> <string name="error_deleting_contact_request">Erro ao excluir solicitação de contato</string> <string name="failed_to_active_user_title">Erro ao trocar de perfil!</string> - <string name="auth_disable_simplex_lock">Desativar Bloqueio SimpleX</string> - <string name="auth_enable_simplex_lock">Ativar Bloqueio SimpleX</string> + <string name="auth_disable_simplex_lock">Desativar o bloqueio SimpleX</string> + <string name="auth_enable_simplex_lock">Ativar bloqueio SimpleX</string> <string name="icon_descr_edited">editado</string> <string name="icon_descr_server_status_error">Erro</string> - <string name="icon_descr_email">E-mail</string> + <string name="icon_descr_email">Email</string> <string name="error_saving_ICE_servers">Erro ao salvar servidores ICE</string> <string name="exit_without_saving">Sair sem salvar</string> - <string name="display_name">Nome de exibição</string> + <string name="display_name">Digite o seu nome:</string> <string name="encrypted_video_call">chamada de vídeo criptografada ponta-a-ponta</string> <string name="integrity_msg_duplicate">mensagem duplicada</string> <string name="status_e2e_encrypted">criptografado ponta-a-ponta</string> <string name="export_database">Exportar banco de dados</string> <string name="total_files_count_and_size">%d arquivo(s) com tamanho total de %s</string> - <string name="error_exporting_chat_database">Erro ao exportar banco de chat</string> - <string name="error_importing_database">Erro ao importar banco de dados de chat</string> - <string name="error_stopping_chat">Erro ao interromper o chat</string> + <string name="error_exporting_chat_database">Erro ao exportar banco de dados do bate-papo</string> + <string name="error_importing_database">Erro ao importar banco de dados do bate-papo</string> + <string name="error_stopping_chat">Erro ao interromper o bate-papo</string> <string name="error_changing_message_deletion">Erro ao alterar configuração</string> <string name="error_encrypting_database">Erro ao criptografar o banco de dados</string> <string name="encrypted_database">Banco de dados criptografado</string> @@ -314,9 +314,9 @@ <string name="enter_passphrase">Digite a senha…</string> <string name="error_with_info">Erro: %s</string> <string name="button_edit_group_profile">Editar perfil do grupo</string> - <string name="icon_descr_expand_role">Expandir seleção de função</string> + <string name="icon_descr_expand_role">Expandir seleção de cargo</string> <string name="error_saving_group_profile">Erro ao salvar o perfil do grupo</string> - <string name="direct_messages">DMs</string> + <string name="direct_messages">Mensagens diretas</string> <string name="feature_enabled">habilitado</string> <string name="feature_enabled_for_contact">habilitado para contato</string> <string name="feature_enabled_for_you">ativado para você</string> @@ -363,9 +363,9 @@ <string name="conn_level_desc_indirect">indireto (%1$s)</string> <string name="incognito">Anônimo</string> <string name="timed_messages">Mensagens que desaparecem</string> - <string name="group_preferences">Preferências de grupo</string> - <string name="disappearing_prohibited_in_this_chat">Mensagens temporárias são proibidas nesse chat.</string> - <string name="group_members_can_send_dms">Os membros do grupo podem enviar DMs.</string> + <string name="group_preferences">Preferências do grupo</string> + <string name="disappearing_prohibited_in_this_chat">Mensagens temporárias são proibidas nesse bate-papo.</string> + <string name="group_members_can_send_dms">Os membros do grupo podem enviar mensagens diretas.</string> <string name="ttl_mth">%dmês</string> <string name="simplex_link_mode_full">Link completo</string> <string name="hide_notification">Ocultar</string> @@ -374,12 +374,12 @@ <string name="notification_preview_mode_hidden">Oculto</string> <string name="how_to_use_your_servers">Como usar seus servidores</string> <string name="import_database_confirmation">Importar</string> - <string name="import_database_question">Importar banco de dados de chat\?</string> - <string name="group_display_name_field">Nome de exibição do grupo:</string> + <string name="import_database_question">Importar banco de dados de bate-papo?</string> + <string name="group_display_name_field">Digite o nome de exibição do grupo:</string> <string name="group_full_name_field">Nome completo do grupo:</string> <string name="v4_2_group_links">Links de grupo</string> <string name="v4_3_improved_privacy_and_security">Privacidade e segurança aprimoradas</string> - <string name="failed_to_parse_chats_title">Falha ao carregar chats</string> + <string name="failed_to_parse_chats_title">Falha ao carregar o bate-papo</string> <string name="file_with_path">Arquivo: %s</string> <string name="file_saved">Arquivo salvo</string> <string name="group_members_can_send_voice">Os membros do grupo podem enviar mensagens de voz.</string> @@ -388,15 +388,15 @@ <string name="notification_display_mode_hidden_desc">Ocultar contato e mensagem</string> <string name="how_to_use_simplex_chat">Como usar</string> <string name="how_to_use_markdown">Como usar markdown</string> - <string name="if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel"><![CDATA[Se você não puder se encontrar pessoalmente, <b>mostre o código QR na videochamada</b> ou compartilhe o link.]]></string> - <string name="if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link"><![CDATA[Se você não puder encontrar pessoalmente, você pode <b>escanear o código QR na videochamada</b> ou seu contato pode compartilhar um link de convite.]]></string> + <string name="if_you_cannot_meet_in_person_show_QR_in_video_call_or_via_another_channel"><![CDATA[Se você não puder se encontrar pessoalmente, <b>mostre o QR code na video chamada</b> ou compartilhe o link.]]></string> + <string name="if_you_cannot_meet_in_person_scan_QR_in_video_call_or_ask_for_invitation_link"><![CDATA[Se você não puder encontrar pessoalmente, você pode <b>escanear o QR code na video chamada</b> ou seu contato pode compartilhar um link de convite.]]></string> <string name="network_disable_socks_info">Se você confirmar, os servidores de mensagens poderão ver seu endereço IP e seu provedor - e quais servidores você está se conectando.</string> <string name="image_descr">Imagem</string> <string name="if_you_received_simplex_invitation_link_you_can_open_in_browser">Se você recebeu o link de convite SimpleX Chat, você pode abri-lo em seu navegador:</string> - <string name="image_saved">Imagem salva na galeria</string> + <string name="image_saved">Imagem salva na Galeria</string> <string name="description_via_group_link_incognito">anônimo via link do grupo</string> <string name="incoming_video_call">Chamada de vídeo recebida</string> - <string name="turn_off_battery_optimization"><![CDATA[Para usá-lo, por favor <b>desative a otimização da bateria</b> para SimpleX na próxima caixa de diálogo. Caso contrário, as notificações serão desativadas.]]></string> + <string name="turn_off_battery_optimization"><![CDATA[Para usá-lo, por favor <b>permite que o SimpleX funcione em segundo plano</b> na próxima caixa de diálogo. Caso contrário, as notificações serão desabilitadas.]]></string> <string name="share_one_time_link">Gerar um link de convite de uso único</string> <string name="file_not_found">Arquivo não encontrado</string> <string name="if_you_choose_to_reject_the_sender_will_not_be_notified">Se você optar por rejeitar o remetente NÃO será notificado.</string> @@ -407,23 +407,23 @@ <string name="icon_descr_flip_camera">Vire a câmera</string> <string name="icon_descr_hang_up">Desligar</string> <string name="settings_section_title_incognito">Modo anônimo</string> - <string name="initial_member_role">Função inicial</string> + <string name="initial_member_role">Cargo inicial</string> <string name="snd_group_event_group_profile_updated">perfil do grupo atualizado</string> <string name="group_member_status_group_deleted">Grupo excluído</string> <string name="incognito_info_protects">O modo Incognito protege sua privacidade usando um novo perfil aleatório para cada contato.</string> - <string name="group_members_can_delete">Os membros do grupo podem excluir mensagens enviadas de forma irreversível.</string> + <string name="group_members_can_delete">Os membros do grupo podem excluir mensagens enviadas de forma irreversível. (24 horas)</string> <string name="ttl_w">%dsemana</string> <string name="v4_3_improved_server_configuration">Configuração de servidor aprimorada</string> <string name="v4_4_french_interface">Interface francesa</string> <string name="callstate_ended">terminou</string> - <string name="allow_accepting_calls_from_lock_screen">Ative as chamadas pela tela de bloqueio nas Configurações.</string> + <string name="allow_accepting_calls_from_lock_screen">Ative as chamadas pela tela de bloqueio nas configurações.</string> <string name="files_and_media_section">Arquivos & mídia</string> <string name="error_updating_link_for_group">Erro ao atualizar o link do grupo</string> <string name="group_invitation_expired">O convite do grupo expirou</string> <string name="file_will_be_received_when_contact_is_online">O arquivo será recebido quando seu contato estiver online, aguarde ou verifique mais tarde!</string> <string name="group_profile_is_stored_on_members_devices">O perfil do grupo é armazenado nos dispositivos dos membros, não nos servidores.</string> <string name="icon_descr_help">ajuda</string> - <string name="how_simplex_works">Como SimpleX funciona</string> + <string name="how_simplex_works">Como o SimpleX funciona</string> <string name="enter_one_ICE_server_per_line">Servidores ICE (um por linha)</string> <string name="ignore">Ignorar</string> <string name="image_will_be_received_when_contact_is_online">A imagem será recebida quando seu contato estiver online, aguarde ou verifique mais tarde!</string> @@ -431,52 +431,52 @@ <string name="receiving_via">Recebendo via</string> <string name="network_status">Status da conexão</string> <string name="network_option_seconds_label">seg</string> - <string name="incognito_info_allows">Permite ter várias conexões anônimas sem nenhum dado compartilhado entre elas em um único perfil de chat.</string> + <string name="incognito_info_allows">Permite ter várias conexões anônimas sem nenhum dado compartilhado entre elas em um único perfil de bate-papo.</string> <string name="chat_preferences_yes">Sim</string> <string name="profile_will_be_sent_to_contact_sending_link">Seu perfil será enviado para o contato do qual você recebeu esse link.</string> <string name="accept_feature_set_1_day">Definir 1 dia</string> <string name="thousand_abbreviation">k</string> - <string name="you_will_join_group">Você se juntará a um grupo ao qual este link se refere e se conectará aos membros do seu grupo.</string> + <string name="you_will_join_group">Você se conectará a todos os membros do grupo.</string> <string name="marked_deleted_description">marcado como excluído</string> <string name="sending_files_not_yet_supported">o envio de arquivos ainda não é suportado</string> <string name="prohibit_sending_voice_messages">Proibir o envio de mensagens de voz.</string> <string name="display_name_invited_to_connect">convidado à conectar</string> <string name="description_you_shared_one_time_link_incognito">você compartilhou um link anônimo de uso único</string> <string name="simplex_link_mode">Links SimpleX</string> - <string name="contact_developers">Por favor, atualize o app e contate os desenvolvedores .</string> + <string name="contact_developers">Por favor, atualize o app e contate os desenvolvedores.</string> <string name="error_smp_test_server_auth">O servidor requer autorização para criar filas, verifique a senha</string> <string name="reveal_verb">Revelar</string> <string name="icon_descr_server_status_pending">Pendente</string> - <string name="prohibit_direct_messages">Proibir o envio de DMs para membros.</string> + <string name="prohibit_direct_messages">Proibir o envio de mensagens diretas para membros.</string> <string name="scan_QR_code">Escanear QR Code</string> <string name="reject_contact_button">Rejeitar</string> <string name="feature_offered_item">ofereceu %s</string> <string name="icon_descr_address">Endereço SimpleX</string> <string name="feature_offered_item_with_param">ofereceu %s: %2s</string> <string name="new_in_version">Novo em %s</string> - <string name="send_us_an_email">Envie-nos um e-mail</string> + <string name="send_us_an_email">Envie-nos um email</string> <string name="smp_servers_scan_qr">Escanear QR code do servidor</string> <string name="your_SMP_servers">Seus servidores SMP</string> <string name="chat_item_ttl_none">nunca</string> <string name="restore_database_alert_title">Restaurar o backup do banco de dados\?</string> <string name="moderated_description">moderado</string> <string name="sender_cancelled_file_transfer">O remetente cancelou a transferência de arquivos.</string> - <string name="network_error_desc">Por favor, cheque sua conexão com a rede com%1$s e tente de novo.</string> + <string name="network_error_desc">Por favor, cheque sua conexão com a rede com %1$s e tente de novo.</string> <string name="simplex_service_notification_text">Recebendo mensagens…</string> - <string name="large_file">Aruivo grande!</string> + <string name="large_file">Arquivo grande!</string> <string name="mark_read">Marcado como lido</string> <string name="you_invited_a_contact">Você convidou seu contato</string> - <string name="invalid_QR_code">Código QR inválido</string> + <string name="invalid_QR_code">QR code inválido</string> <string name="icon_descr_more_button">Mais</string> <string name="you_will_be_connected_when_group_host_device_is_online">Você será conectado ao grupo quando o dispositivo do host do grupo estiver online, por favor aguarde ou verifique mais tarde!</string> <string name="this_string_is_not_a_connection_link">Essa string não é um link de conexão!</string> <string name="network_use_onion_hosts_prefer">Quando disponível</string> - <string name="app_version_code">Build do app: %s</string> + <string name="app_version_code">Compilação do aplicativo: %s</string> <string name="save_and_notify_contact">Salvar e notificar contato</string> <string name="callstate_received_answer">resposta recebida…</string> <string name="read_more_in_github">Leia mais no nosso repositório do GitHub.</string> - <string name="paste_the_link_you_received">Colar link recebido</string> - <string name="onboarding_notifications_mode_off">Quando o app está executando</string> + <string name="paste_the_link_you_received">Cole o link que você recebeu</string> + <string name="onboarding_notifications_mode_off">Quando o aplicativo está em execução</string> <string name="onboarding_notifications_mode_periodic">Periódico</string> <string name="your_calls">Suas chamadas</string> <string name="old_database_archive">Arquivo de banco de dados antigo</string> @@ -494,9 +494,9 @@ <string name="your_privacy">Sua privacidade</string> <string name="join_group_question">Juntar-se ao grupo\?</string> <string name="leave_group_button">Sair</string> - <string name="failed_to_create_user_duplicate_desc">Você já possui um perfil de chat com o mesmo nome. Por favor escolha outro nome.</string> + <string name="failed_to_create_user_duplicate_desc">Você já possui um perfil de bate-papo com o mesmo nome. Por favor escolha outro nome.</string> <string name="please_check_correct_link_and_maybe_ask_for_a_new_one">Por favor, cheque se você usou o link correto ou peça ao seu contato para enviar outro.</string> - <string name="auth_open_chat_console">Abrir console de chat</string> + <string name="auth_open_chat_console">Abrir console de bate-papo</string> <string name="group_preview_join_as">juntar-se como %s</string> <string name="ask_your_contact_to_enable_voice">Por favor, peça ao seu contato para ativar o envio de mensagens de voz.</string> <string name="ok">OK</string> @@ -506,25 +506,25 @@ <string name="icon_descr_call_rejected">Chamada rejeitada</string> <string name="restore_database">Restaurar o backup do banco de dados</string> <string name="section_title_for_console">PARA CONSOLE</string> - <string name="run_chat_section">EXECUTAR CHAT</string> + <string name="run_chat_section">EXECUTAR BATE-PAPO</string> <string name="stop_chat_confirmation">Parar</string> <string name="set_password_to_export">Definir senha para exportar</string> <string name="restart_the_app_to_use_imported_chat_database">Reinicie o aplicativo para usar o banco de dados do chat importado.</string> - <string name="restart_the_app_to_create_a_new_chat_profile">Reinicie o app para criar um novo perfil de chat.</string> + <string name="restart_the_app_to_create_a_new_chat_profile">Reinicie o aplicativo para criar um novo perfil de chat.</string> <string name="delete_files_and_media_desc">Essa ação não pode ser desfeita - todos os arquivos e mídias recebidos e enviados serão excluídos. Imagens de baixa resolução permanecerão.</string> - <string name="you_must_use_the_most_recent_version_of_database">Você deve usar a versão mais recente de seu banco de dados de chat SOMENTE em um dispositivo, caso contrário, você pode parar de receber as mensagens de alguns contatos.</string> + <string name="you_must_use_the_most_recent_version_of_database">Você deve usar a versão mais recente de seu banco de dados de bate-papo SOMENTE em um dispositivo, caso contrário, você pode parar de receber as mensagens de alguns contatos.</string> <string name="no_received_app_files">Sem arquivos enviados ou recebidos</string> <string name="messages_section_title">Mensagens</string> <string name="messages_section_description">Esta configuração aplica-se às mensagens no seu perfil de chat atual</string> <string name="keychain_error">erro na Keychain</string> - <string name="store_passphrase_securely_without_recover">Por favor, guarde a senha em um local seguro, você não poderá acessar o chat se perdê-lo.</string> + <string name="store_passphrase_securely_without_recover">Por favor, guarde a senha em um local seguro, você não poderá acessar o bate-papo se perdê-lo.</string> <string name="store_passphrase_securely">Guarde a senha em um local seguro, você NÃO poderá alterá-la se a perder.</string> <string name="restore_database_alert_confirm">Restaurar</string> <string name="restore_database_alert_desc">Por favor, digite a senha antiga depois de recuperar o backup do banco de dados. Essa ação não pode ser desfeita.</string> <string name="wrong_passphrase_title">Senha incorreta</string> - <string name="you_can_start_chat_via_setting_or_by_restarting_the_app">Você pode iniciar o chat via Configurações / Banco de dados ou reiniciando o aplicativo.</string> + <string name="you_can_start_chat_via_setting_or_by_restarting_the_app">Você pode iniciar o bate-papo via Configurações / Banco de dados ou reiniciando o aplicativo.</string> <string name="alert_title_group_invitation_expired">Convite expirado!</string> - <string name="you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved">Você deixará de receber mensagens deste grupo. O histórico do chat será preservado.</string> + <string name="you_will_stop_receiving_messages_from_this_group_chat_history_will_be_preserved">Você deixará de receber mensagens deste grupo. O histórico do bate-papo será preservado.</string> <string name="rcv_group_event_user_deleted">removeu você</string> <string name="rcv_group_event_member_added">convidado%1$s</string> <string name="snd_conn_event_switch_queue_phase_completed_for_member">você alterou o endereço para %s</string> @@ -532,7 +532,7 @@ <string name="group_member_role_member">membro</string> <string name="button_leave_group">Sair do grupo</string> <string name="group_info_section_title_num_members">%1$s MEMBROS</string> - <string name="group_info_member_you">você%1$s</string> + <string name="group_info_member_you">você: %1$s</string> <string name="info_row_local_name">Nome local</string> <string name="save_group_profile">Salvar perfil do grupo</string> <string name="network_option_ping_interval">Intervalo de PING</string> @@ -541,7 +541,7 @@ <string name="users_delete_with_connections">Conexões de servidor e perfil</string> <string name="your_preferences">Suas preferências</string> <string name="set_group_preferences">Definir preferências de grupo</string> - <string name="only_you_can_delete_messages">Somente você pode excluir irreversivelmente as mensagens (seu contato pode marcá-las para exclusão).</string> + <string name="only_you_can_delete_messages">Somente você pode excluir irreversivelmente as mensagens (seu contato pode marcá-las para exclusão). (24 horas)</string> <string name="message_deletion_prohibited_in_chat">A exclusão irreversível de mensagens é proibida neste grupo.</string> <string name="v4_4_live_messages_desc">Os destinatários vêem as atualizações conforme você as digita.</string> <string name="v4_5_reduced_battery_usage">Uso da bateria reduzido</string> @@ -560,11 +560,11 @@ <string name="sender_may_have_deleted_the_connection_request">O remetente pode ter excluído a solicitação de conexão.</string> <string name="periodic_notifications_disabled">Notificações periódicas estão desativadas!</string> <string name="service_notifications_disabled">As notificações instantâneas estão desativadas!</string> - <string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery"><![CDATA[Para preservar sua privacidade, em vez de notificações push, o aplicativo tem um<b>SimpleXserviço em segundo plano</b> - ele usa uma porcentagem da bateria por dia.]]></string> + <string name="to_preserve_privacy_simplex_has_background_service_instead_of_push_notifications_it_uses_a_few_pc_battery"><![CDATA[Para preservar sua privacidade, em vez de notificações push, o aplicativo tem um<b>serviço em segundo plano SimpleX</b> - ele usa uma porcentagem da bateria por dia.]]></string> <string name="notifications_mode_off">Executa quando o aplicativo está aberto</string> <string name="icon_descr_sent_msg_status_sent">enviado</string> <string name="icon_descr_sent_msg_status_send_failed">o envio falhou</string> - <string name="your_chats">Chats</string> + <string name="your_chats">Bate-papos</string> <string name="paste_button">Colar</string> <string name="one_time_link">Link de convite de uso único</string> <string name="chat_with_the_founder">Enviar perguntas e idéias</string> @@ -577,16 +577,16 @@ <string name="join_group_incognito_button">Entrar como anônimo</string> <string name="icon_descr_add_members">Convidar membros</string> <string name="rcv_group_event_invited_via_your_group_link">convidado via seu link de grupo</string> - <string name="snd_group_event_changed_role_for_yourself">você alterou sua função para %s</string> - <string name="snd_group_event_changed_member_role">você mudou a função de %s para %s</string> - <string name="new_member_role">Nova função de membro</string> + <string name="snd_group_event_changed_role_for_yourself">você alterou seu cargo para %s</string> + <string name="snd_group_event_changed_member_role">você mudou o cargo de %s para %s</string> + <string name="new_member_role">Novo cargo de membro</string> <string name="remove_member_confirmation">Remover</string> <string name="member_info_section_title_member">MEMBRO</string> <string name="member_will_be_removed_from_group_cannot_be_undone">O membro será removido do grupo - isso não pode ser desfeito!</string> - <string name="role_in_group">Função</string> + <string name="role_in_group">Cargo</string> <string name="sending_via">Enviando via</string> <string name="prohibit_sending_disappearing_messages">Proibir o envio de mensagens temporárias.</string> - <string name="message_deletion_prohibited">A exclusão irreversível de mensagens é proibida neste chat.</string> + <string name="message_deletion_prohibited">A exclusão irreversível de mensagens é proibida neste bate-papo.</string> <string name="prohibit_sending_voice">Proibir o envio de mensagens de voz.</string> <string name="v4_3_voice_messages_desc">No máximo 40 segundos, recebido instantaneamemte.</string> <string name="observer_cant_send_message_title">Você não pode enviar mensagens!</string> @@ -596,12 +596,12 @@ <string name="smp_servers_preset_address">Endereço do servidor pré-definido</string> <string name="many_people_asked_how_can_it_deliver"><![CDATA[Muitos perguntaram:<i> se SimpleX não tem identificadores de usuários, como ele pode mandar mensagens\?</i>]]></string> <string name="reject">Rejeitar</string> - <string name="integrity_msg_skipped">%1$d mensagens ignoradas</string> - <string name="protect_app_screen">Proteger a tela do app</string> + <string name="integrity_msg_skipped">%1$d mensagem(s) ignorada(s)</string> + <string name="protect_app_screen">Proteger a tela do aplicativo</string> <string name="send_link_previews">Enviar prévias de links</string> <string name="privacy_and_security">Privacidade e segurança</string> <string name="join_group_button">Junte-se</string> - <string name="you_can_share_group_link_anybody_will_be_able_to_connect">Você pode compartilhar um link ou um código QR - qualquer um poderá entrar no grupo. Você não perderá membros do grupo se você deletá-los mais tarde.</string> + <string name="you_can_share_group_link_anybody_will_be_able_to_connect">Você pode compartilhar um link ou um QR code - qualquer um poderá entrar no grupo. Você não perderá membros do grupo se você deletá-los mais tarde.</string> <string name="users_delete_data_only">Somente dados de perfil local</string> <string name="only_you_can_send_disappearing">Somente você pode enviar mensagens temporárias.</string> <string name="only_your_contact_can_send_disappearing">Somente seu contato pode enviar mensagens temporárias.</string> @@ -618,7 +618,7 @@ <string name="reply_verb">Responder</string> <string name="auth_log_in_using_credential">Inicie sessão com a sua credencial</string> <string name="message_delivery_error_title">Erro na entrega da mensagem</string> - <string name="message_delivery_error_desc">Porvavelmente esse contato excluiu a conexão com você.</string> + <string name="message_delivery_error_desc">Provavelmente esse contato excluiu a conexão com você.</string> <string name="save_verb">Salvar</string> <string name="delete_message_cannot_be_undone_warning">A mensagem será excluída - isso não pode ser desfeito!</string> <string name="delete_message_mark_deleted_warning">A mensagem será marcada para exclusão. O(s) destinatário(s) poderá(ão) revelar esta mensagem.</string> @@ -627,7 +627,7 @@ <string name="images_limit_desc">Apenas 10 imagens podem ser enviadas ao mesmo tempo</string> <string name="notifications">Notificações</string> <string name="text_field_set_contact_placeholder">Definir nome do contato…</string> - <string name="switch_receiving_address_desc">O endereço de recebimento será alterado para um servidor diferente. A mudança de endereço terminará após o remetente entrar on-line.</string> + <string name="switch_receiving_address_desc">O endereço de recebimento será alterado para um servidor diferente. A mudança de endereço terminará após o remetente ficar online.</string> <string name="send_verb">Enviar</string> <string name="reset_verb">Redefinir</string> <string name="live_message">Mensagem ao vivo!</string> @@ -639,17 +639,18 @@ <string name="mute_chat">Mutar</string> <string name="set_contact_name">Definir nome do contato</string> <string name="invalid_contact_link">Link inválido!</string> - <string name="this_QR_code_is_not_a_link">Esse código QR não é um link!</string> - <string name="your_chat_profile_will_be_sent_to_your_contact">Seu perfil de chat será enviado para seu -\ncontato</string> + <string name="this_QR_code_is_not_a_link">Esse QR code não é um link!</string> + <string name="your_chat_profile_will_be_sent_to_your_contact">Seu perfil de bate-papo será enviado +\npara seu contato</string> <string name="you_will_be_connected_when_your_contacts_device_is_online">Você será conectado quando o dispositivo do seu contato estiver online, aguarde ou verifique mais tarde!</string> <string name="how_to">Como</string> <string name="smp_servers_test_failed">Teste do servidor falhou!</string> <string name="network_use_onion_hosts_no_desc">Onion hosts não serão usados.</string> - <string name="network_use_onion_hosts_required_desc">Os hosts Onion serão necessários para a conexão.</string> + <string name="network_use_onion_hosts_required_desc">Os hosts Onion serão necessários para a conexão. +\nAtenção: você não será capaz de se conectar aos servidores sem um endereço .onion</string> <string name="network_use_onion_hosts_required_desc_in_alert">Os hosts Onion serão necessários para a conexão.</string> <string name="core_version">Versão principal: v%s</string> - <string name="read_more_in_github_with_link"><![CDATA[Leia mais no nosso<font color="#0088ff"> repositório do GitHub</font>.]]></string> + <string name="read_more_in_github_with_link"><![CDATA[Leia mais no nosso <font color="#0088ff">repositório do GitHub</font>.]]></string> <string name="onboarding_notifications_mode_subtitle">Pode ser mudado mais tarde via configurações.</string> <string name="contact_wants_to_connect_via_call">%1$s quer se conectar com você via</string> <string name="status_no_e2e_encryption">sem criptografia ponta-a-ponta</string> @@ -662,21 +663,21 @@ <string name="remove_passphrase_from_keychain">Remover senha da Keystore\?</string> <string name="new_passphrase">Nova senha…</string> <string name="database_restore_error">Erro na restauração do banco de dados</string> - <string name="open_chat">Abrir chat</string> - <string name="save_passphrase_and_open_chat">Salvar senha e abrir chat</string> - <string name="rcv_group_event_member_deleted">removido%1$s</string> + <string name="open_chat">Abrir bate-papo</string> + <string name="save_passphrase_and_open_chat">Salvar senha e abrir bate-papo</string> + <string name="rcv_group_event_member_deleted">removido %1$s</string> <string name="network_option_protocol_timeout">Tempo limite do protocolo</string> <string name="feature_received_prohibited">recebido, proibido</string> <string name="user_hide">Ocultar</string> <string name="enter_password_to_show">Digite a senha na pesquisa</string> <string name="dont_show_again">Não mostrar novamente</string> - <string name="only_your_contact_can_delete">Somente seu contato pode excluir irreversivelmente mensagens (você pode marcá-las para exclusão).</string> + <string name="only_your_contact_can_delete">Somente seu contato pode excluir irreversivelmente mensagens (você pode marcá-las para exclusão). (24 horas)</string> <string name="v4_6_chinese_spanish_interface">Interface chinesa e espanhola</string> <string name="v4_6_reduced_battery_usage">Maior redução no uso da bateria</string> <string name="v4_6_reduced_battery_usage_descr">Mais melhorias chegarão em breve!</string> <string name="settings_section_title_you">VOCÊ</string> <string name="settings_section_title_messages">MENSAGENS E ARQUIVOS</string> - <string name="your_chat_database">Seu banco de dados de chat</string> + <string name="your_chat_database">Seu banco de dados de bate-papo</string> <string name="snd_group_event_member_deleted">Você removeu %1$s</string> <string name="group_member_status_removed">removido</string> <string name="group_member_status_left">saiu</string> @@ -688,7 +689,7 @@ <string name="remove_passphrase">Remover</string> <string name="wrong_passphrase">Senha do banco de dados incorreta</string> <string name="save_archive">Salvar arquivo</string> - <string name="restore_passphrase_not_found_desc">Senha não encontrada na Keystore, por favor digite-a manualmente. Isso pode ter ocorrido se você recuperou os dados do app usando uma ferramenta de backup. Se esse não é o caso, por favor contate os desenvolvedores.</string> + <string name="restore_passphrase_not_found_desc">Senha não encontrada na Keystore, por favor digite-a manualmente. Isso pode ter ocorrido se você recuperou os dados do app usando uma ferramenta de backup. Se esse não é o caso, por favor, contate os desenvolvedores.</string> <string name="youve_accepted_group_invitation_connecting_to_inviting_group_member">Você se juntou a este grupo. Conectando-se a um membro convidado do grupo.</string> <string name="leave_group_question">Sair do grupo\?</string> <string name="alert_message_no_group">Este grupo não existe mais.</string> @@ -697,14 +698,14 @@ <string name="chat_preferences_no">não</string> <string name="v4_6_group_moderation_descr">Agora administradores podem: \n- excluir mensagens de membros. -\n- desativar membros (função de \"observador\")</string> +\n- desativar membros (cargo de \"observador\")</string> <string name="v4_6_group_moderation">Moderação do grupo</string> <string name="v4_6_group_welcome_message">Mensagem de boas-vindas do grupo</string> <string name="database_downgrade">Desatualizar banco de dados</string> <string name="mtr_error_different">migração diferente no aplicativo/banco de dados: %s / %s</string> <string name="invite_to_group_button">Convidar para o grupo</string> <string name="no_contacts_to_add">Sem contatos para adicionar</string> - <string name="member_role_will_be_changed_with_notification">A função será alterada para \"%s\". Todos no grupo serão notificados.</string> + <string name="member_role_will_be_changed_with_notification">O cargo será alterada para \"%s\". Todos no grupo serão notificados.</string> <string name="user_mute">Mutar</string> <string name="only_you_can_send_voice">Somente você pode enviar mensagens de voz.</string> <string name="only_your_contact_can_send_voice">Somente seu contato pode enviar mensagens de voz.</string> @@ -713,7 +714,7 @@ <string name="v4_3_irreversible_message_deletion">Exclusão irreversível de mensagens</string> <string name="v4_4_disappearing_messages_desc">Mensagens enviadas serão excluídas depois do tempo definido.</string> <string name="v4_4_live_messages">Mensagens ao vivo</string> - <string name="v4_5_multiple_chat_profiles">Vários perfis de chat</string> + <string name="v4_5_multiple_chat_profiles">Vários perfis de bate-papo</string> <string name="v4_6_hidden_chat_profiles">Perfis de chat ocultos</string> <string name="v4_5_message_draft">Rascunho de mensagem</string> <string name="v4_5_message_draft_descr">Preservar o último rascunho, com anexos.</string> @@ -726,7 +727,7 @@ <string name="to_verify_compare">Para verificar a criptografia de ponta-a-ponta com seu contato, compare (ou escaneie) o código em seus dispositivos.</string> <string name="smp_save_servers_question">Salvar servidores\?</string> <string name="smp_servers_per_user">Os servidores para novas conexões do seu perfil de chat atual</string> - <string name="rate_the_app">Avalie o app</string> + <string name="rate_the_app">Avalie o aplicativo</string> <string name="developer_options">IDs de banco de dados e opção de isolamento de transporte.</string> <string name="profile_is_only_shared_with_your_contacts">O perfil é compartilhado apenas com seus contatos.</string> <string name="secret_text">segredo</string> @@ -737,7 +738,7 @@ <string name="rcv_group_event_member_left">saiu</string> <string name="incompatible_database_version">Versão do banco de dados incompatível</string> <string name="button_remove_member">Remover membro</string> - <string name="group_main_profile_sent">Seu perfil de chat será enviado aos membros do grupo</string> + <string name="group_main_profile_sent">Seu perfil de bate-papo será enviado aos membros do grupo</string> <string name="make_profile_private">Torne o perfil privado!</string> <string name="v4_2_security_assessment">Avaliação de segurança</string> <string name="v4_5_multiple_chat_profiles_descr">Nomes diferentes, avatares e isolamento de transporte.</string> @@ -748,7 +749,7 @@ <string name="save_profile_password">Salvar senha do perfil</string> <string name="icon_descr_call_missed">Chamada perdida</string> <string name="save_passphrase_in_keychain">Salvar senha na Keystore</string> - <string name="database_is_not_encrypted">Seu banco de dados de chat não está criptografado - defina uma senha para protegê-lo.</string> + <string name="database_is_not_encrypted">Seu banco de dados de bate-papo não está criptografado - defina uma senha para protegê-lo.</string> <string name="group_invitation_item_description">convite para o grupo%1$s</string> <string name="alert_title_cant_invite_contacts_descr">Você esta usando um perfil anônimo para este grupo - para evitar compartilhar seu perfil principal, convidar contatos não é permitido</string> <string name="invalid_migration_confirmation">Confirmação de migração inválida</string> @@ -758,7 +759,7 @@ <string name="snd_group_event_user_left">você saiu</string> <string name="whats_new">Novidades</string> <string name="v4_6_audio_video_calls">Chamadas de áudio e vídeo</string> - <string name="v4_6_hidden_chat_profiles_descr">Proteja seus perfis de chat com uma senha!</string> + <string name="v4_6_hidden_chat_profiles_descr">Proteja seus perfis de bate-papo com uma senha!</string> <string name="this_text_is_available_in_settings">Este texto está disponível nas configurações</string> <string name="scan_code">Escanear código</string> <string name="network_use_onion_hosts_no_desc_in_alert">Hosts Onion não serão usados.</string> @@ -778,7 +779,7 @@ <string name="delete_profile">Excluir perfil</string> <string name="profile_password">Senha de perfil</string> <string name="videos_limit_title">Excesso de vídeos!</string> - <string name="icon_descr_video_asked_to_receive">Solicitou receber o vídeo</string> + <string name="icon_descr_video_asked_to_receive">Pediu para receber o vídeo</string> <string name="videos_limit_desc">Apenas 10 vídeos podem ser enviados ao mesmo tempo</string> <string name="contact_sent_large_file">Seu contato enviou um arquivo maior que o tamanho máximo permitido (%1$s).</string> <string name="file_will_be_received_when_contact_completes_uploading">O arquivo será recebido quando seu contato concluir o upload.</string> @@ -799,7 +800,7 @@ <string name="add_contact_or_create_group">Começar novo chat</string> <string name="smp_servers_use_server">Usar servidor</string> <string name="you_need_to_allow_to_send_voice">Você precisa permitir que seu contato envie mensagens de voz para poder enviá-las também.</string> - <string name="your_chat_profiles">Seu perfil de chat</string> + <string name="your_chat_profiles">Seu perfil de bate-papo</string> <string name="smp_servers_preset_server">Servidor pré-definido</string> <string name="scan_code_from_contacts_app">Escaneie o código de segurança do aplicativo do seu contato.</string> <string name="icon_descr_send_message">Enviar mensagem</string> @@ -813,21 +814,22 @@ <string name="error_saving_user_password">Erro ao salvar a senha do usuário</string> <string name="hide_profile">Ocultar perfil</string> <string name="callstate_received_confirmation">confirmação recebida…</string> - <string name="relay_server_protects_ip">O servidor relay protege seu endereço IP, mas pode observar a duração da chamada.</string> + <string name="relay_server_protects_ip">O servidor de relay protege seu endereço IP, mas pode observar a duração da chamada.</string> <string name="settings_section_title_experimenta">EXPERIMENTAL</string> <string name="snd_conn_event_switch_queue_phase_completed">você alterou o endereço</string> <string name="database_upgrade">Atualização do banco de dados</string> - <string name="member_role_will_be_changed_with_invitation">A função será alterada para \"%s\". O membro receberá um novo convite.</string> + <string name="member_role_will_be_changed_with_invitation">O cargo será alterado para \"%s\". O membro receberá um novo convite.</string> <string name="only_group_owners_can_change_prefs">Somente os proprietários do grupo podem alterar as preferências do grupo.</string> <string name="button_add_welcome_message">Adicionar mensagem de boas-vindas</string> <string name="group_welcome_title">Mensagem de boas-vindas</string> - <string name="button_send_direct_message">Enviar DM</string> + <string name="button_send_direct_message">Enviar mensagem direta</string> <string name="muted_when_inactive">Mutado quando inativo!</string> <string name="you_will_still_receive_calls_and_ntfs">Você ainda receberá chamadas e notificações de perfis silenciados quando eles estiverem ativos.</string> <string name="delete_chat_profile">Excluir perfil de chat</string> <string name="save_servers_button">Salvar</string> <string name="star_on_github">Estrela no GitHub</string> - <string name="alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry">Seu contato precisa estar online para completar a conexão. Você pode cancelar esta conexão e remover o contato (e tentar mais tarde com um novo link).</string> + <string name="alert_text_connection_pending_they_need_to_be_online_can_delete_and_retry">Seu contato precisa estar online para completar a conexão. +\nVocê pode cancelar esta conexão e remover o contato (e tentar mais tarde com um novo link).</string> <string name="app_name">SimpleX</string> <string name="only_group_owners_can_enable_voice">Somente o proprietários de grupo podem ativar mensagens de voz</string> <string name="description_you_shared_one_time_link">você compartilhou um link de uso único</string> @@ -845,30 +847,30 @@ <string name="share_invitation_link">Compartilhar link de uso único</string> <string name="network_use_onion_hosts">Usar hosts .onion</string> <string name="video_call_no_encryption">chamada de vídeo (não criptografada ponta-a-ponta)</string> - <string name="stop_chat_question">Parar chat\?</string> + <string name="stop_chat_question">Parar bate-papo?</string> <string name="simplex_link_mode_browser">Pelo navegador</string> - <string name="you_are_already_connected_to_vName_via_this_link">Você já está conectado a%1$s.</string> + <string name="you_are_already_connected_to_vName_via_this_link">Você já está conectado a %1$s.</string> <string name="database_initialization_error_desc">O banco de dados não está funcionando corretamente. Toque para saber mais</string> - <string name="waiting_for_image">Aguardando imagem</string> - <string name="show_QR_code">Mostrar código QR</string> + <string name="waiting_for_image">Aguardando a imagem</string> + <string name="show_QR_code">Mostrar QR code</string> <string name="you_can_also_connect_by_clicking_the_link"><![CDATA[Você também pode se conectar clicando no link. Se abrir no navegador, clique em <b>Abrir no aplicativo móvel</b>.]]></string> <string name="smp_servers_test_servers">Servidores de teste</string> <string name="update_network_session_mode_question">Atualizar o modo de isolamento de transporte\?</string> <string name="icon_descr_video_on">Vídeo ativado</string> - <string name="waiting_for_file">Aguardando arquivo</string> + <string name="waiting_for_file">Aguardando o arquivo</string> <string name="chat_help_tap_button">Toque no botão</string> - <string name="to_start_a_new_chat_help_header">Para começar um novo chat</string> + <string name="to_start_a_new_chat_help_header">Para começar um novo bate-papo</string> <string name="la_notice_turn_on">Ligar</string> - <string name="welcome">Bem-vindo!</string> + <string name="welcome">Bem-vindo(a)!</string> <string name="next_generation_of_private_messaging">A próxima geração de mensageiros privados</string> <string name="settings_section_title_socks">PROXY SOCKS</string> <string name="database_backup_can_be_restored">A tentativa de alterar a senha do banco de dados não foi concluída.</string> - <string name="stop_chat_to_export_import_or_delete_chat_database">Pare o chat para exportar, importar ou excluir o banco de dados do chat. Você não poderá receber e enviar mensagens enquanto o chat estiver interrompido.</string> - <string name="stop_chat_to_enable_database_actions">Pare o chat para ativar ações no banco de dados.</string> + <string name="stop_chat_to_export_import_or_delete_chat_database">Pare o bate-papo para exportar, importar ou excluir o banco de dados do chat. Você não poderá receber e enviar mensagens enquanto o chat estiver interrompido.</string> + <string name="stop_chat_to_enable_database_actions">Pare o bate-papo para ativar ações no banco de dados.</string> <string name="chat_item_ttl_seconds">%s segundo(s)</string> <string name="unknown_database_error_with_info">Erro de banco de dados desconhecido: %s</string> <string name="unknown_error">Erro desconhecido</string> - <string name="group_invitation_tap_to_join">Toque para se juntar</string> + <string name="group_invitation_tap_to_join">Toque para juntar-se</string> <string name="group_invitation_tap_to_join_incognito">Toque para entrar no modo anônimo</string> <string name="you_are_invited_to_group">Você está convidado para o grupo</string> <string name="updating_settings_will_reconnect_client_to_all_servers">A atualização das configurações reconectará o cliente a todos os servidores.</string> @@ -885,15 +887,15 @@ <string name="error_smp_test_failed_at_step">O teste falhou na etapa %s.</string> <string name="notifications_mode_periodic">Inicia periodicamente</string> <string name="icon_descr_sent_msg_status_unauthorized_send">envio não autorizado</string> - <string name="tap_to_start_new_chat">Toque para iniciar um novo chat</string> - <string name="you_have_no_chats">Você não tem chats</string> + <string name="tap_to_start_new_chat">Toque para iniciar um novo bate-papo</string> + <string name="you_have_no_chats">Você não tem bate-papos</string> <string name="callstate_waiting_for_answer">aguardando resposta…</string> - <string name="your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one">Seu banco de dados de chat atual será EXCLUÍDO e SUBSTITUÍDO pelo importado. + <string name="your_current_chat_database_will_be_deleted_and_replaced_with_the_imported_one">Seu banco de dados de bate-papo atual será EXCLUÍDO e SUBSTITUÍDO pelo importado. \nEsta ação não pode ser desfeita - seu perfil, contatos, mensagens e arquivos serão perdidos de forma irreversível.</string> <string name="update_database_passphrase">Atualizar senha do banco de dados</string> <string name="rcv_group_event_updated_group_profile">perfil de grupo atualizado</string> <string name="switch_verb">Trocar</string> - <string name="group_is_decentralized">O grupo é totalmente descentralizado – é visível apenas para os membros.</string> + <string name="group_is_decentralized">Totalmente decentralizado - visível apenas para os membros.</string> <string name="you_are_observer">você é um observador</string> <string name="voice_message_with_duration">Mensagem de voz (%1$s)</string> <string name="share_link">Compartilhar link</string> @@ -910,12 +912,12 @@ <string name="notification_preview_mode_message_desc">Mostrar contato e mensagem</string> <string name="notification_preview_mode_contact_desc">Mostrar somente contato</string> <string name="share_verb">Compartilhar</string> - <string name="auth_stop_chat">Parar chat</string> + <string name="auth_stop_chat">Parar bate-papo</string> <string name="auth_unlock">Desbloquear</string> <string name="moderate_message_will_be_deleted_warning">A mensagem será excluída para todos os membros.</string> <string name="moderate_message_will_be_marked_warning">A mensagem será marcada como moderada para todos os membros.</string> <string name="share_image">Compartilhar mídia…</string> - <string name="icon_descr_waiting_for_image">Aguardando imagem</string> + <string name="icon_descr_waiting_for_image">Aguardando a imagem</string> <string name="share_file">Compartilhar arquivo…</string> <string name="image_decoding_exception_desc">A imagem não pode ser decodificada. Por favor, tente uma imagem diferente ou entre em contato com os desenvolvedores.</string> <string name="voice_message">Mensagem de voz</string> @@ -946,29 +948,29 @@ <string name="user_unhide">Mostrar</string> <string name="user_unmute">Desmutar</string> <string name="v4_3_voice_messages">Mensagens de voz</string> - <string name="you_can_connect_to_simplex_chat_founder"><![CDATA[Você pode <font color="#0088ff">se conectar aos desenvolvedores doSimpleX para fazer qualquer pergunta e receber atualizações</font>.]]></string> + <string name="you_can_connect_to_simplex_chat_founder"><![CDATA[Você pode <font color="#0088ff">se conectar aos desenvolvedores do SimpleX Chat para fazer qualquer pergunta e receber atualizações</font>.]]></string> <string name="connection_you_accepted_will_be_cancelled">A conexão que você aceitou será cancelada!</string> <string name="contact_you_shared_link_with_wont_be_able_to_connect">O contato com o qual você compartilhou este link NÃO será capaz de se conectar!</string> <string name="icon_descr_simplex_team">Equipe SimpleX</string> - <string name="use_simplex_chat_servers__question">Usar servidoresSimpleX\?</string> - <string name="using_simplex_chat_servers">Usando servidores SimpleX.</string> + <string name="use_simplex_chat_servers__question">Usar servidores SimpleX Chat?</string> + <string name="using_simplex_chat_servers">Usando servidores SimpleX Chat.</string> <string name="show_developer_options">Mostrar opções para desenvolvedores</string> <string name="v4_6_audio_video_calls_descr">Suporte bluetooth e outras melhorias.</string> - <string name="to_reveal_profile_enter_password">Para revelar seu perfil oculto, digite uma senha em um campo de busca em sua página de perfis de chat.</string> - <string name="upgrade_and_open_chat">Atualizar e abrir chat</string> - <string name="database_downgrade_warning">Aviso: você pode perder alguns dados!</string> + <string name="to_reveal_profile_enter_password">Para revelar seu perfil oculto, digite uma senha em um campo de busca em sua página de perfis de bate-papo.</string> + <string name="upgrade_and_open_chat">Atualizar e abrir bate-papo</string> + <string name="database_downgrade_warning">Atenção: você pode perder alguns dados!</string> <string name="you_rejected_group_invitation">Você rejeitou um convite de grupo</string> <string name="icon_descr_received_msg_status_unread">não lida</string> <string name="share_message">Compartilhar mensagem…</string> - <string name="personal_welcome">Bem-vindo%1$s!</string> + <string name="personal_welcome">Bem-vindo(a) %1$s!</string> <string name="group_preview_you_are_invited">você está convidado para o grupo</string> <string name="update_onion_hosts_settings_question">Atualizar configuração de hosts .onion\?</string> - <string name="use_chat">Usar chat</string> + <string name="use_chat">Usar bate-papo</string> <string name="voice_prohibited_in_this_chat">Mensagens de voz são proibidas neste chat.</string> <string name="video_descr">Vídeo</string> <string name="icon_descr_video_snd_complete">Vídeo enviado</string> - <string name="icon_descr_waiting_for_video">Aguardando vídeo</string> - <string name="waiting_for_video">Aguardando vídeo</string> + <string name="icon_descr_waiting_for_video">Aguardando o vídeo</string> + <string name="waiting_for_video">Aguardando o vídeo</string> <string name="verify_security_code">Verificar código de segurança</string> <string name="video_will_be_received_when_contact_completes_uploading">O vídeo será recebido quando seu contato concluir o upload.</string> <string name="video_will_be_received_when_contact_is_online">O vídeo será recebido quando seu contato estiver online, aguarde ou verifique mais tarde!</string> @@ -980,7 +982,7 @@ <string name="callstate_starting">iniciando…</string> <string name="callstate_waiting_for_confirmation">aguardando confirmação…</string> <string name="we_do_not_store_contacts_or_messages_on_servers">Não armazenamos nenhum dos seus contatos ou mensagens (uma vez entregues) nos servidores.</string> - <string name="alert_title_skipped_messages">Mensagens ignoradas</string> + <string name="alert_title_skipped_messages">Mensagens omitidas</string> <string name="tap_to_activate_profile">Toque para ativar o perfil.</string> <string name="unhide_chat_profile">Mostrar perfil de chat</string> <string name="unhide_profile">Mostrar perfil</string> @@ -990,36 +992,37 @@ <string name="smp_servers_your_server">Seu servidor</string> <string name="chat_lock">Bloqueio SimpleX</string> <string name="error_smp_test_certificate">Possivelmente, a impressão digital do certificado no endereço do servidor está incorreta</string> - <string name="you_sent_group_invitation">Você enviou convite de grupo</string> + <string name="you_sent_group_invitation">Você enviou um convite de grupo</string> <string name="incognito_random_profile">Seu perfil aleatório</string> <string name="auth_simplex_lock_turned_on">Bloqueio SimpleX ativado</string> <string name="your_simplex_contact_address">Seu endereço SimpleX</string> <string name="network_options_reset_to_defaults">Redefinir para os padrões</string> <string name="image_descr_link_preview">imagem de pré-visualização do link</string> - <string name="la_notice_to_protect_your_information_turn_on_simplex_lock_you_will_be_prompted_to_complete_authentication_before_this_feature_is_enabled">Para proteger suas informações, ative o bloqueio SimpleX. Você será solicitado a completar a autenticação antes que este recurso seja ativado.</string> - <string name="opensource_protocol_and_code_anybody_can_run_servers">Protocolo de código aberto – qualquer um pode executar os servidores.</string> + <string name="la_notice_to_protect_your_information_turn_on_simplex_lock_you_will_be_prompted_to_complete_authentication_before_this_feature_is_enabled">Para proteger suas informações, ative o bloqueio SimpleX. +\nVocê será solicitado a completar a autenticação antes que este recurso seja ativado.</string> + <string name="opensource_protocol_and_code_anybody_can_run_servers">Protocolo de código aberto – qualquer um pode hospedar os servidores.</string> <string name="theme_light">Claro</string> <string name="chat_preferences_contact_allows">O contato permite</string> <string name="chat_preferences_on">ativado</string> <string name="simplex_link_contact">Endereço de contato</string> - <string name="simplex_link_group">Link do grupo</string> - <string name="simplex_link_invitation">Convite de uso único</string> + <string name="simplex_link_group">Link do grupo SimpleX</string> + <string name="simplex_link_invitation">Convite de uso único SimpleX</string> <string name="mobile_tap_open_in_mobile_app_then_tap_connect_in_app"><![CDATA[📱 móvel: toque em <b>Abrir no aplicativo móvel</b> e toque em <b>Conectar</b> no aplicativo.]]></string> <string name="ntf_channel_calls">Chamadas</string> <string name="ntf_channel_messages">Mensagens</string> <string name="la_notice_title_simplex_lock">Bloqueio SimpleX</string> <string name="markdown_help">Ajuda com Markdown</string> <string name="onboarding_notifications_mode_service">Instantânea</string> - <string name="open_simplex_chat_to_accept_call">Abrao chat do SimpleX para aceitar a chamada</string> + <string name="open_simplex_chat_to_accept_call">Abra o bate-papo do SimpleX para aceitar a chamada</string> <string name="call_connection_via_relay">via relay</string> <string name="feature_off">desativado</string> - <string name="downgrade_and_open_chat">Desatualizar e abrir o chat</string> + <string name="downgrade_and_open_chat">Desatualizar e abrir o bate-papo</string> <string name="chat_preferences_off">desativado</string> - <string name="settings_section_title_support">APOIE SIMPLEX</string> + <string name="settings_section_title_support">APOIE SIMPLEX CHAT</string> <string name="enable_automatic_deletion_message">Esta ação não pode ser desfeita - as mensagens enviadas e recebidas antes do selecionado serão excluídas. Pode levar vários minutos.</string> <string name="confirm_database_upgrades">Confirme as atualizações do banco de dados</string> - <string name="only_client_devices_store_contacts_groups_e2e_encrypted_messages"><![CDATA[Perfis de usuário, contatos, grupos e mensagens enviadas com <b>criptografia de ponta-a-ponta</b> são armazenados somente no seu dispositivo.]]></string> - <string name="thank_you_for_installing_simplex">Obrigado por instalar SimpleX!</string> + <string name="only_client_devices_store_contacts_groups_e2e_encrypted_messages"><![CDATA[Somente o cliente dos dispositivos armazenam perfis de usuários, contatos, grupos e mensagens enviadas com <b>criptografia de ponta a ponta em duas camadas</b>.]]></string> + <string name="thank_you_for_installing_simplex">Obrigado por instalar o SimpleX Chat!</string> <string name="the_messaging_and_app_platform_protecting_your_privacy_and_security">A plataforma de mensagens que protege sua privacidade e segurança.</string> <string name="invite_prohibited_description">Você está tentando convidar um contato com quem compartilhou um perfil anônimo para o grupo no qual está usando seu perfil principal</string> <string name="you_control_servers_to_receive_your_contacts_to_send"><![CDATA[Você controla por meio de qual(is) servidor(es) <b>receber</b> as mensagens, seus contatos controlam os servidores que você usa para enviar mensagens.]]></string> @@ -1057,7 +1060,7 @@ <string name="network_socks_toggle_use_socks_proxy">Usar proxy SOCKS</string> <string name="host_verb">Hospedar</string> <string name="disable_onion_hosts_when_not_supported"><![CDATA[Definir <i>Usar hosts .onion</i> para não se o proxy SOCKS não oferecer suporte a eles.]]></string> - <string name="port_verb">Migrar</string> + <string name="port_verb">Porta</string> <string name="confirm_passcode">Confirmar senha</string> <string name="incorrect_passcode">Senha incorreta</string> <string name="lock_after">Bloquear após</string> @@ -1072,7 +1075,7 @@ <string name="authentication_cancelled">Autenticação cancelada</string> <string name="enable_lock">Habilitar bloqueio</string> <string name="passcode_changed">Senha alterada!</string> - <string name="you_can_turn_on_lock">Você pode ativar o bloqueio SimpleX via Configurações.</string> + <string name="you_can_turn_on_lock">Você pode ativar o bloqueio SimpleX via configurações.</string> <string name="alert_title_msg_bad_hash">Hash de mensagem incorreta</string> <string name="alert_text_msg_bad_hash">O hash da mensagem anterior é diferente.</string> <string name="alert_text_decryption_error_n_messages_failed_to_decrypt">%1$d descriptografia das mensagens falhou</string> @@ -1090,7 +1093,7 @@ <string name="stop_file__confirm">Parar</string> <string name="revoke_file__message">O arquivo será excluído dos servidores.</string> <string name="revoke_file__title">Revogar arquivo\?</string> - <string name="alert_text_decryption_error_too_many_skipped">%1$d mensagens ignoradas</string> + <string name="alert_text_decryption_error_too_many_skipped">%1$d mensagens ignoradas.</string> <string name="audio_video_calls">Chamadas de áudio/vídeo</string> <string name="calls_prohibited_with_this_contact">Chamadas de áudio/vídeo são proibidas.</string> <string name="available_in_v51">" @@ -1105,14 +1108,14 @@ <string name="v5_0_large_files_support_descr">Rápido e sem esperar até que o remetente esteja online!</string> <string name="v5_0_polish_interface">interface polonesa</string> <string name="v5_0_app_passcode_descr">Defina-o em vez da autenticação do sistema.</string> - <string name="v5_0_polish_interface_descr">Obrigado aos usuários – contribuam via Weblate!</string> - <string name="v5_0_large_files_support">Vídeos e arquivos de até 1gb</string> + <string name="v5_0_polish_interface_descr">Obrigado aos usuários – contribua via Weblate!</string> + <string name="v5_0_large_files_support">Vídeos e arquivos de até 1GB</string> <string name="gallery_image_button">Imagem</string> - <string name="decryption_error">erro de descriptografia</string> + <string name="decryption_error">Erro de descriptografia</string> <string name="revoke_file__confirm">Revogar</string> <string name="learn_more_about_address">Sobre o endereço SimpleX</string> <string name="color_secondary_variant">Secundária adicional</string> - <string name="color_primary_variant">Acento adicional</string> + <string name="color_primary_variant">Realçe adicional</string> <string name="one_time_link_short">Link de uso único</string> <string name="add_address_to_your_profile">Adicione o endereço ao seu perfil, para que seus contatos possam compartilhá-lo com outras pessoas. A atualização do perfil será enviada aos seus contatos.</string> <string name="create_address_and_let_people_connect">Crie um endereço para permitir que as pessoas se conectem com você.</string> @@ -1122,7 +1125,7 @@ <string name="address_section_title">Endereço</string> <string name="dark_theme">Tema escuro</string> <string name="color_background">Fundo</string> - <string name="all_your_contacts_will_remain_connected_update_sent">Todos os seus contactos permanecerão ligados. A atualização do perfil será enviada para seus contatos.</string> + <string name="all_your_contacts_will_remain_connected_update_sent">Todos os seus contatos permanecerão conectados. A atualização do perfil será enviada para seus contatos.</string> <string name="auto_accept_contact">Aceitação automática</string> <string name="customize_theme_title">Personalizar o tema</string> <string name="enter_welcome_message">Digite a mensagem de boas-vindas…</string> @@ -1132,7 +1135,7 @@ <string name="import_theme_error_desc">Certifique-se que o arquivo tenha a sintaxe YAML correta. Exporte o tema para ter um exemplo da estrutura do arquivo do tema.</string> <string name="export_theme">Exportar tema</string> <string name="import_theme_error">Erro ao importar tema</string> - <string name="color_surface">Menus e alertas</string> + <string name="color_surface">Menus & Alertas</string> <string name="color_received_message">Mensagem recebida</string> <string name="color_sent_message">Mensagem enviada</string> <string name="color_title">Título</string> @@ -1202,8 +1205,8 @@ <string name="info_row_updated_at">Registro atualizado em</string> <string name="share_text_database_id">ID do banco de dados: %d</string> <string name="share_text_deleted_at">Excluído em: %s</string> - <string name="info_row_disappears_at">Desaparece em</string> - <string name="share_text_disappears_at">Desaparece em: %s</string> + <string name="info_row_disappears_at">Desaparecerá</string> + <string name="share_text_disappears_at">Desaparecerá: %s</string> <string name="info_row_moderated_at">Moderado em</string> <string name="share_text_moderated_at">Moderado em: %s</string> <string name="share_text_received_at">Recebido em: %s</string> @@ -1213,11 +1216,11 @@ <string name="allow_your_contacts_adding_message_reactions">Permitir que seus contatos adicionem reações à mensagens.</string> <string name="both_you_and_your_contact_can_add_message_reactions">Você e seu contato podem adicionar reações à mensagens.</string> <string name="group_members_can_add_message_reactions">Os membros do grupo podem adicionar reações às mensagens.</string> - <string name="message_reactions_prohibited_in_this_chat">Reações à mensagens são proibidas neste chat.</string> + <string name="message_reactions_prohibited_in_this_chat">Reações à mensagens são proibidas neste bate-papo.</string> <string name="prohibit_message_reactions">Proibir reações à mensagens.</string> <string name="custom_time_picker_custom">personalizado</string> <string name="whats_new_read_more">Ler mais</string> - <string name="v5_1_self_destruct_passcode_descr">Todos seus dados são apagados quando digitado</string> + <string name="v5_1_self_destruct_passcode_descr">Todos seus dados são apagados quando inserido.</string> <string name="v5_1_message_reactions_descr">Finalmente, nós os temos! 🚀</string> <string name="v5_1_message_reactions">Reações à mensagens</string> <string name="v5_1_self_destruct_passcode">Senha de auto-destruição</string> @@ -1228,7 +1231,7 @@ <string name="whats_new_thanks_to_users_contribute_weblate">Obrigado aos usuários – contribua via Weblate!</string> <string name="v5_1_better_messages_descr">- mensagens de voz de até 5 minutos \n- tempo personalizado para desaparecer. -\n- histórico de edição.</string> +\n- edição de histórico.</string> <string name="custom_time_unit_weeks">semanas</string> <string name="allow_message_reactions_only_if">Permitir reações à mensagens somente se o seu contato permitir.</string> <string name="v5_1_custom_themes_descr">Personalize e compartilhe temas de cores.</string> @@ -1240,23 +1243,23 @@ <string name="info_row_sent_at">Enviado em</string> <string name="change_self_destruct_passcode">Alterar senha de auto-destruição</string> <string name="if_you_enter_self_destruct_code">Se você digitar sua senha de auto-destruição ao abrir o aplicativo:</string> - <string name="item_info_no_text">Sem texto</string> + <string name="item_info_no_text">sem texto</string> <string name="non_fatal_errors_occured_during_import">Alguns erros não-fatais ocurreram durante importação - pode ver o console de Chat para mais detalhes.</string> <string name="search_verb">Pesquisar</string> <string name="la_mode_off">Desativado</string> <string name="files_and_media">Arquivos e mídia</string> <string name="error_aborting_address_change">Erro ao cancelar alteração de endereço</string> - <string name="abort_switch_receiving_address_question">Anular a mudança de endereço\?</string> - <string name="abort_switch_receiving_address_confirm">Anular</string> + <string name="abort_switch_receiving_address_question">Abortar a mudança de endereço?</string> + <string name="abort_switch_receiving_address_confirm">Abortar</string> <string name="abort_switch_receiving_address_desc">A alteração de endereço será cancelada. O endereço de recebimento antigo será usado.</string> <string name="shutdown_alert_question">Desligar\?</string> - <string name="abort_switch_receiving_address">Anular alteração de endereço</string> + <string name="abort_switch_receiving_address">Abortar alteração de endereço</string> <string name="network_option_protocol_timeout_per_kb">Tempo limite do protocolo por KB</string> <string name="shutdown_alert_desc">As notificações deixarão de funcionar até que você reinicie o aplicativo</string> <string name="in_reply_to">Em resposta a</string> <string name="only_owners_can_enable_files_and_media">Somente os proprietários do grupo podem habilitar arquivos e mídia.</string> <string name="sync_connection_force_desc">A criptografia está funcionando e o novo acordo de criptografia não é necessário. Pode resultar em erros de conexão!</string> - <string name="snd_conn_event_ratchet_sync_started">concordando criptografia para %s</string> + <string name="snd_conn_event_ratchet_sync_started">concordando com criptografia para %s…</string> <string name="conn_event_ratchet_sync_agreed">criptografia concordada</string> <string name="conn_event_ratchet_sync_allowed">renegociação de criptografia permitida</string> <string name="rcv_conn_event_verification_code_reset">código de segurança alterado</string> @@ -1264,12 +1267,12 @@ <string name="sender_at_ts">%s em %s</string> <string name="files_are_prohibited_in_group">Arquivos e mídia são proibidos neste grupo.</string> <string name="prohibit_sending_files">Proibir o envio de arquivos e mídia.</string> - <string name="snd_conn_event_ratchet_sync_ok">criptografia ok para %s</string> + <string name="snd_conn_event_ratchet_sync_ok">criptografia OK para %s</string> <string name="fix_connection_not_supported_by_group_member">Correção não suportada pelo membro do grupo</string> - <string name="conn_event_ratchet_sync_started">concordando criptografia…</string> + <string name="conn_event_ratchet_sync_started">concordando com criptografia…</string> <string name="allow_to_send_files">Permitir o envio de arquivos e mídia.</string> <string name="settings_section_title_app">APP</string> - <string name="conn_event_ratchet_sync_ok">criptografia ok</string> + <string name="conn_event_ratchet_sync_ok">criptografia OK</string> <string name="conn_event_ratchet_sync_required">renegociação de criptografia necessária</string> <string name="snd_conn_event_ratchet_sync_agreed">criptografia concordada para %s</string> <string name="snd_conn_event_ratchet_sync_allowed">renegociação de criptografia permitida para %s</string> @@ -1284,41 +1287,41 @@ <string name="settings_shutdown">Desligar</string> <string name="fix_connection">Corrigir conexão</string> <string name="fix_connection_question">Corrigir conexão\?</string> - <string name="no_filtered_chats">Sem chats filtrados</string> + <string name="no_filtered_chats">Sem bate-papo filtrados</string> <string name="sync_connection_force_confirm">Renegociar</string> <string name="unfavorite_chat">Desfavoritar</string> <string name="sync_connection_force_question">Renegociar a criptografia\?</string> <string name="settings_restart_app">Reiniciar</string> - <string name="delivery_receipts_title">Confirmações de entrega!</string> - <string name="delivery_receipts_are_disabled">As confirmações de entrega estão desabilitadas!</string> + <string name="delivery_receipts_title">Recibos de entrega!</string> + <string name="delivery_receipts_are_disabled">Os recibos de entrega estão desabilitadas!</string> <string name="receipts_contacts_enable_for_all">Ativar para todos</string> <string name="receipts_contacts_disable_for_all">Desativar para todos</string> - <string name="receipts_contacts_title_disable">Desabilitar as confirmações\?</string> + <string name="receipts_contacts_title_disable">Desabilitar os recibos?</string> <string name="receipts_contacts_disable_keep_overrides">Desativar (mantém alterações)</string> <string name="receipts_contacts_enable_keep_overrides">Ativar (mantém alterações)</string> - <string name="receipts_contacts_title_enable">Ativar as confirmações\?</string> - <string name="v5_2_favourites_filter">Encontrar conversas rápido</string> + <string name="receipts_contacts_title_enable">Ativar recibos?</string> + <string name="v5_2_favourites_filter">Encontrar conversas mais rápido</string> <string name="receipts_section_contacts">Contatos</string> - <string name="settings_section_title_delivery_receipts">ENVIAR CONFIRMAÇÕES DE ENTREGA PARA</string> + <string name="settings_section_title_delivery_receipts">ENVIAR RECIBOS DE ENTREGA PARA</string> <string name="receipts_contacts_override_disabled">Enviar confirmações está desativado para %d contatos.</string> <string name="receipts_contacts_override_enabled">Enviar confirmações está ativado para %d contatos.</string> <string name="send_receipts">Enviar confirmações</string> <string name="v5_2_more_things">Mais algumas coisas</string> <string name="v5_2_disappear_one_message_descr">Até mesmo desabilitado na conversa.</string> - <string name="v5_2_favourites_filter_descr">Filtrar chats não lidos e favoritos.</string> - <string name="v5_2_fix_encryption_descr">Corrigir encriptação depois de restaurar os backups.</string> + <string name="v5_2_favourites_filter_descr">Filtrar bate-papo não lidos e favoritos.</string> + <string name="v5_2_fix_encryption_descr">Corrigir criptografia depois de restaurar os backups.</string> <string name="v5_2_fix_encryption">Manter suas conexões</string> <string name="v5_2_disappear_one_message">Fazer uma mensagem desaparecer</string> <string name="v5_2_message_delivery_receipts">Confirmações de entrega de mensagens!</string> <string name="v5_2_more_things_descr">- entregas de mensagens mais estáveis. -\n- grupos melhores. +\n- grupos um pouco melhores. \n- e mais!</string> <string name="dont_enable_receipts">Não ative</string> <string name="enable_receipts_all">Ativar</string> <string name="sending_delivery_receipts_will_be_enabled">Enviar confirmações de entrega serão ativadas para todos os contatos.</string> - <string name="error_enabling_delivery_receipts">Ocorreu um erro ao ativar as confirmações de entrega!</string> + <string name="error_enabling_delivery_receipts">Ocorreu um erro ao ativar as recibos de entrega!</string> <string name="choose_file_title">Escolher arquivo</string> - <string name="connect_via_link_incognito">Conectar incógnito</string> + <string name="connect_via_link_incognito">Conectar anônimamente</string> <string name="turn_off_battery_optimization_button">Permitir</string> <string name="disable_notifications_button">Desativar notificações</string> <string name="system_restricted_background_in_call_title">Sem chamadas de fundo</string> @@ -1335,10 +1338,304 @@ <string name="in_developing_title">Em breve!</string> <string name="delivery">Entrega</string> <string name="no_info_on_delivery">Nenhuma informação de entrega</string> - <string name="sending_delivery_receipts_will_be_enabled_all_profiles">Enviar recibos de entrega será ativado para todos os contatos em todos os perfis de chat visíveis.</string> + <string name="sending_delivery_receipts_will_be_enabled_all_profiles">Enviar recibos de entrega serão habilitados para todos os contatos em todos os perfis visíveis.</string> <string name="rcv_group_event_2_members_connected">%s e %s conectados</string> <string name="connect_via_member_address_alert_title">Conectar diretamente\?</string> - <string name="no_selected_chat">Sem chat selecionado</string> + <string name="no_selected_chat">Nenhum bate-papo selecionado</string> <string name="privacy_message_draft">Rascunho de mensagem</string> <string name="send_receipts_disabled">desativado</string> + <string name="system_restricted_background_desc">SimpleX não pode ser executado em segundo plano. Você receberá as notificações somente quando o aplicativo estiver em execução.</string> + <string name="system_restricted_background_warn"><![CDATA[Para ativar notificações, selecione <b>Uso de bateria do aplicativo</b> / <b>Irrestrito</b> nas configurações do aplicativo.]]></string> + <string name="v5_3_new_interface_languages">6 novos idiomas de interface</string> + <string name="v5_5_private_notes">Notas privadas</string> + <string name="v5_5_private_notes_descr">Com arquivos criptografados e mídia</string> + <string name="v5_5_simpler_connect_ui">Colar o link para conectar!</string> + <string name="v5_5_simpler_connect_ui_descr">A barra de pesquisa aceita links de convite.</string> + <string name="v5_5_join_group_conversation">Participe de conversas em grupo</string> + <string name="not_compatible">Não compatível!</string> + <string name="invalid_qr_code">QR code inválido</string> + <string name="correct_name_to">Corrigir nome para %s?</string> + <string name="rcv_group_event_member_created_contact">diretamente conectado</string> + <string name="connect_plan_this_is_your_own_simplex_address">Este é o seu próprio endereço SimpleX!</string> + <string name="desktop_connection_terminated">Conexão terminada</string> + <string name="connect_plan_this_is_your_link_for_group_vName"><![CDATA[Este link é seu link para o grupo <b>%1$s</b>!]]></string> + <string name="remote_ctrl_was_disconnected_title">Conexão interrompida</string> + <string name="remote_ctrl_error_busy">Desktop está ocupado</string> + <string name="blocked_by_admin_items_description">%d mensagens bloqueadas pelo admnistrador</string> + <string name="blocked_by_admin_item_description">bloqueado pelo admnistrador</string> + <string name="note_folder_local_display_name">Notas privadas</string> + <string name="system_restricted_background_in_call_desc">O aplicativo pode ser fechado após 1 minuto em segundo plano.</string> + <string name="search_or_paste_simplex_link">Procurar e colar link SimpleX</string> + <string name="add_contact_button_to_create_link_or_connect_via_link"><![CDATA[<b>Adicionar contato</b>: para criar um novo link de convite, ou conectar pelo link que você recebeu.]]></string> + <string name="share_this_1_time_link">Compartilhe este link de convite único</string> + <string name="alert_text_encryption_renegotiation_failed">Renegociação de criptografia falhou.</string> + <string name="rcv_group_and_other_events">e %d outros eventos</string> + <string name="rcv_group_events_count">%d eventos de grupo</string> + <string name="group_members_2">%s e %s</string> + <string name="info_row_created_at">Criado em</string> + <string name="enable_sending_recent_history">Enviar até as 100 últimas mensagens para novos membros.</string> + <string name="link_a_mobile">Vincular um celular</string> + <string name="remote_host_was_disconnected_title">Conexão interrompida</string> + <string name="waiting_for_desktop">Aguardando o desktop…</string> + <string name="connected_desktop">Desktop conectado</string> + <string name="linked_desktops">Desktops vínculados</string> + <string name="remote_host_error_missing"><![CDATA[Dispositivo Móvel <b>%s</b> está faltando]]></string> + <string name="open_port_in_firewall_title">Abrir porta na firewall</string> + <string name="open_port_in_firewall_desc">Para permitir que um aplicativo móvel se conecte ao desktop, abra esta porta em seu firewall, se estiver ativado</string> + <string name="remote_host_error_timeout"><![CDATA[Tempo limite atingido durante a conexão com o móvel <b>%s</b>]]></string> + <string name="remote_host_error_busy"><![CDATA[Dispositivo Móvel <b>%s</b> está ocupado]]></string> + <string name="remote_host_error_inactive"><![CDATA[Dispositivo Móvel <b>%s</b> está inativo]]></string> + <string name="remote_host_error_bad_state"><![CDATA[A conexão com o dispositivo móvel <b>%s</b> está em mau estado]]></string> + <string name="remote_ctrl_error_bad_state">A conexão com o desktop está em mau estado</string> + <string name="remote_ctrl_error_disconnected">Desktop foi desconectado</string> + <string name="remote_ctrl_error_bad_invitation">Desktop tem um código de convite errado</string> + <string name="remote_ctrl_error_bad_version">O desktop tem uma versão não suportada. Por favor, certifique-se de usar a mesma versão em ambos os dispositivos</string> + <string name="agent_critical_error_title">Erro crítico</string> + <string name="agent_internal_error_title">Erro interno</string> + <string name="restart_chat_button">Reiniciar o bate-papo</string> + <string name="error_creating_member_contact">Erro ao criar contato de membro</string> + <string name="you_can_enable_delivery_receipts_later">Você pode habilitar mais tarde nas configurações</string> + <string name="you_can_enable_delivery_receipts_later_alert">Você pode habilitá-los mais tarde pelo aplicativo nas configurações de Privacidade & Segurança</string> + <string name="verify_code_with_desktop">Verificar código com o desktop</string> + <string name="new_desktop"><![CDATA[<i>(novo)</i>]]></string> + <string name="connect_plan_repeat_join_request">Repetir o pedido para se juntar?</string> + <string name="in_developing_desc">Este recurso ainda não é compatível. Experimente o próximo lançamento.</string> + <string name="connect_plan_you_have_already_requested_connection_via_this_address">Você já pediu para se conectar por este endereço!</string> + <string name="connect_plan_already_connecting">Você já está se conectando!</string> + <string name="connect_plan_connect_to_yourself">Conectar-se a você mesmo?</string> + <string name="connect_plan_open_group">Abrir grupo</string> + <string name="connect_plan_you_are_already_joining_the_group_via_this_link">Você já está se conectando ao grupo por este link.</string> + <string name="connect_plan_already_joining_the_group">Você já está entrando no grupo!</string> + <string name="connect_plan_connect_via_link">Conectar via link?</string> + <string name="call_service_notification_audio_call">Chamada de áudio</string> + <string name="call_service_notification_end_call">Finalizar chamada</string> + <string name="call_service_notification_video_call">Chamada de vídeo</string> + <string name="expand_verb">Expandir</string> + <string name="compose_send_direct_message_to_connect">Enviar mensagem direta para conectar</string> + <string name="tap_to_scan">Toque para escanear</string> + <string name="new_chat">Novo bate-papo</string> + <string name="or_show_this_qr_code">Ou mostrar este código</string> + <string name="keep_invitation_link">Manter</string> + <string name="keep_unused_invitation_question">Manter convite não utilizado?</string> + <string name="or_scan_qr_code">Ou escanear o QR code</string> + <string name="tap_to_paste_link">Toque para colar o link</string> + <string name="the_text_you_pasted_is_not_a_link">O texto que você colou não é um link SimpleX.</string> + <string name="you_can_view_invitation_link_again">Você pode visualizar o código de convite novamente nos detalhes de conexão.</string> + <string name="developer_options_section">Opções de desenvolvedor</string> + <string name="show_slow_api_calls">Mostrar chamadas de API lentas</string> + <string name="you_can_make_address_visible_via_settings">Você pode tornar isso visível aos seus contatos SimpleX nas configurações.</string> + <string name="encrypt_local_files">Criptografar arquivos locais</string> + <string name="rcv_group_event_1_member_connected">%s conectou</string> + <string name="error_blocking_member_for_all">Erro ao bloquear membro para todos</string> + <string name="connect_via_member_address_alert_desc">Pedido de conexão será enviado para este membro do grupo.</string> + <string name="welcome_message_is_too_long">Mensagem de boas vindas é muito grande</string> + <string name="v5_3_simpler_incognito_mode_descr">Alterne para navegação anônima ao conectar.</string> + <string name="linked_mobiles">Dispositivos móveis vínculados</string> + <string name="remote_host_disconnected_from"><![CDATA[Desconectado do dispositivo móvel <b>%s</b> pelo motivo: %s]]></string> + <string name="bad_desktop_address">Endereço de desktop incorreto</string> + <string name="waiting_for_mobile_to_connect">Aguardando o dispositivo móvel conectar:</string> + <string name="verify_connections">Verificar conexões</string> + <string name="desktop_address">Endereço do desktop</string> + <string name="connect_use_current_profile">Usar perfil atual</string> + <string name="connect_use_new_incognito_profile">Usar novo perfil anônimo</string> + <string name="error_creating_message">Erro ao criar mensagem</string> + <string name="connect_plan_group_already_exists">Grupo já existe!</string> + <string name="connect_plan_you_are_already_in_group_vName"><![CDATA[Você já está no grupo <b>%1$s</b>.]]></string> + <string name="show_internal_errors">Mostrar erros internos</string> + <string name="receipts_groups_override_enabled">O envio de recibos está habilitado para %d grupos</string> + <string name="start_chat_question">Iniciar bate-papo?</string> + <string name="recent_history_is_not_sent_to_new_members">Histórico não é enviado para novos membros.</string> + <string name="v5_3_discover_join_groups">Descobrir e se juntar a grupos</string> + <string name="refresh_qr_code">Recarregar</string> + <string name="remote_host_error_bad_version"><![CDATA[Dispositivo Móvel <b>%s</b> tem uma versão não suportada. Por favor, certifique-se de usar a mesma versão em ambos os dispositivos]]></string> + <string name="failed_to_create_user_invalid_title">Nome de exibição inválido!</string> + <string name="failed_to_create_user_invalid_desc">Este nome de exibição é inválido. Por favor escolha outro nome.</string> + <string name="blocked_item_description">bloqueado</string> + <string name="non_content_uri_alert_title">Caminho de arquivo inválido</string> + <string name="non_content_uri_alert_text">Você compartilhou um caminho de arquivo inválido. Informe o problema para os desenvolvedores do aplicativo.</string> + <string name="message_too_large">A mensagem é muito grande</string> + <string name="app_was_crashed">Visualização travou</string> + <string name="marked_deleted_items_description">%d mensagens marcadas como excluídas</string> + <string name="delete_messages__question">Deletar %d mensagens?</string> + <string name="contact_tap_to_connect">Toque para conectar</string> + <string name="v5_3_simpler_incognito_mode">Modo anônimo simplificado</string> + <string name="found_desktop">Desktop encontrado</string> + <string name="random_port">Aleatório</string> + <string name="database_migration_in_progress">Migração do banco de dados em progresso. +\nIsso pode levar alguns minutos.</string> + <string name="moderated_items_description">%1$d mensagens moderadas por %2$s</string> + <string name="blocked_items_description">%d mensagens bloqueadas</string> + <string name="error_deleting_note_folder">Erro ao deletar notas privadas</string> + <string name="system_restricted_background_in_call_warn"><![CDATA[Para fazer ligações no plano de fundo, escolha <b>Uso de bateria do aplicativo</b> / <b>Irrestrito</b> nas configurações do aplicativo.]]></string> + <string name="send_receipts_disabled_alert_msg">Este grupo tem mais de %1$d membros, recibos de entrega não são enviados.</string> + <string name="loading_remote_file_title">Carregando o arquivo</string> + <string name="rcv_direct_event_contact_deleted">contato deletado</string> + <string name="error">Erro</string> + <string name="this_device">Este dispositivo</string> + <string name="discover_on_network">Descobrir via rede local</string> + <string name="group_member_status_unknown_short">desconhecido</string> + <string name="share_text_created_at">Criado em: %s</string> + <string name="button_remove_member_question">Remover membro?</string> + <string name="saved_message_title">Mensagem salva</string> + <string name="block_member_button">Bloquear membro</string> + <string name="remove_member_button">Remover membro</string> + <string name="block_for_all">Bloquear para todos</string> + <string name="block_for_all_question">Bloquear membro para todos?</string> + <string name="unblock_member_confirmation">Desbloquear</string> + <string name="unblock_member_button">Desbloquear membro</string> + <string name="unblock_member_question">Desbloquear membro?</string> + <string name="unblock_member_desc">Mensagens de %s serão exibidas!</string> + <string name="unblock_for_all">Desbloquear para todos</string> + <string name="unblock_for_all_question">Desbloquear membro para todos?</string> + <string name="create_group_button">Criar grupo</string> + <string name="v5_3_encrypt_local_files_descr">O aplicativo criptografa novos arquivos locais (exceto vídeos).</string> + <string name="v5_3_discover_join_groups_descr">- conecte-se ao serviço de diretório (BETA)! +\n- recibos de entrega (até 20 membros). +\n- mais rápido e mais estável.</string> + <string name="v5_5_message_delivery">Entrega de mensagens aprimorada</string> + <string name="v5_5_join_group_conversation_descr">Histórico recente e bot de diretório aprimorado.</string> + <string name="v5_2_message_delivery_receipts_descr">O segundo tick que perdemos! ✅</string> + <string name="v5_5_message_delivery_descr">Com uso de bateria reduzido.</string> + <string name="connected_to_mobile">Conectado ao dispositivo móvel</string> + <string name="disconnect_remote_hosts">Desconectar dispositivos móveis</string> + <string name="v5_5_new_interface_languages">UI húngara e turca</string> + <string name="this_device_version"><![CDATA[<i>(este dispositivo v%s)</i>]]></string> + <string name="scan_from_mobile">Escanear pelo dispositivo móvel</string> + <string name="remote_ctrl_disconnected_with_reason">Desconectado pelo motivo: %s</string> + <string name="connecting_to_desktop">Conectando ao desktop</string> + <string name="connect_to_desktop">Conectar ao desktop</string> + <string name="desktop_incompatible_version">Versão incompatível</string> + <string name="open_on_mobile_and_scan_qr_code"><![CDATA[Abra <i>Use no desktop</i> no aplicativo móvel e escaneie o QR code.]]></string> + <string name="paste_desktop_address">Colar o endereço de desktop</string> + <string name="no_connected_mobile">Nenhum celular conectado</string> + <string name="connect_plan_join_your_group">Se juntar ao seu grupo?</string> + <string name="connect_plan_repeat_connection_request">Repetir o pedido de conexão?</string> + <string name="connect_plan_you_are_already_connecting_via_this_one_time_link">Você já está se conectando por este código de uso único!</string> + <string name="agent_critical_error_desc">Por favor informe isto aos desenvolvedores: +\n%s +\n +\nÉ recomendado reiniciar o aplicativo.</string> + <string name="privacy_show_last_messages">Mostrar últimas mensagens</string> + <string name="receipts_section_description_1">Eles podem ser substituídos nas configurações de contato e grupo.</string> + <string name="save_passphrase_in_settings">Salvar senha nas configurações</string> + <string name="settings_is_storing_in_clear_text">A senha é armazenada nas configurações como um texto simples.</string> + <string name="passphrase_will_be_saved_in_settings">A senha será armazenada nas configurações como um texto simples após você mudar ela ou reiniciar o aplicativo.</string> + <string name="rcv_group_event_member_unblocked">desbloqueado %s</string> + <string name="group_members_n">%s, %s e %d membros</string> + <string name="rcv_group_event_n_members_connected">%s, %s e %d outros membros conectaram</string> + <string name="rcv_group_event_3_members_connected">%s, %s e %s conectaram</string> + <string name="snd_group_event_member_unblocked">você desbloqueou %s</string> + <string name="v5_3_new_interface_languages_descr">Árabe, búlgaro, finlandês, hebraico, tailandês e ucraniano - graças aos usuários e ao Weblate.</string> + <string name="use_random_passphrase">Usar senha aleatória</string> + <string name="setup_database_passphrase">Configurar senha do banco de dados</string> + <string name="la_app_passcode">Senha do aplicativo</string> + <string name="add_contact_tab">Adicionar contato</string> + <string name="clear_note_folder_warning">Todas as mensagens serão deletadas - isto não poderá ser desfeito!</string> + <string name="block_member_desc">Todas as novas mensagens de %s serão ocultadas!</string> + <string name="member_blocked_by_admin">Bloqueado pelo admnistrador</string> + <string name="rcv_group_event_member_blocked">bloqueado %s</string> + <string name="create_group_button_to_create_new_group"><![CDATA[<b>Criar grupo</b>: para criar um novo grupo.]]></string> + <string name="block_member_confirmation">Bloquear</string> + <string name="member_info_member_blocked">bloqueado</string> + <string name="code_you_scanned_is_not_simplex_link_qr_code">O código que você escaneou não é um QR code SimpleX.</string> + <string name="unable_to_open_browser_desc">O navegador padrão é necessário para chamadas. Configure o navegador padrão no sistema e compartilhe mais informações com os desenvolvedores.</string> + <string name="receipts_section_description">Essas configurações são para o seu perfil atual</string> + <string name="video_decoding_exception_desc">O vídeo não pode ser decodificado. Por favor, tente com um vídeo diferente ou contate os desenvolvedores.</string> + <string name="connect_plan_this_is_your_own_one_time_link">Este é o seu próprio link de uso único!</string> + <string name="remote_ctrl_error_timeout">Tempo limite atingido durante a conexão com o desktop</string> + <string name="recent_history_is_sent_to_new_members">Até as 100 últimas mensagens são enviadas para novos membros.</string> + <string name="snd_group_event_member_blocked">você bloqueou %s</string> + <string name="connect__your_profile_will_be_shared">Seu perfil %1$s será compartilhado.</string> + <string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[Você já está se conectando ao grupo <b>%1$s</b>.]]></string> + <string name="block_member_question">Bloquear membro?</string> + <string name="socks_proxy_setting_limitations"><![CDATA[<b>Atenção</b>: retransmissões de mensagens e arquivos são conectadas via proxy SOCKS. As chamadas e o envio de visualizações de links usam conexão direta.]]></string> + <string name="camera_not_available">Câmera não disponível</string> + <string name="connected_mobile">Dispositivo móvel conectado</string> + <string name="chat_is_stopped_you_should_transfer_database">O bate-papo foi interrompido. Se você já usou esse banco de dados em outro dispositivo, deverá transferi-lo de volta antes de iniciar o bate-papo.</string> + <string name="clear_note_folder_question">Limpar notas privadas?</string> + <string name="connected_to_desktop">Conectado ao desktop</string> + <string name="creating_link">Criando link…</string> + <string name="connect_with_contact_name_question">Conectar com %1$s?</string> + <string name="remote_ctrl_error_inactive">Desktop está inativo</string> + <string name="database_encryption_will_be_updated_in_settings">A senha de criptografia do banco de dados será atualizada e armazenada nas configurações.</string> + <string name="create_chat_profile">Criar perfil de bate-papo</string> + <string name="database_will_be_encrypted_and_passphrase_stored_in_settings">O banco de dados será criado e a senha será armazenada nas configurações.</string> + <string name="delete_and_notify_contact">Deletar e notificar contato</string> + <string name="desktop_device">Desktop</string> + <string name="desktop_app_version_is_incompatible">A versão do aplicativo desktop %s não é compatível com este aplicativo.</string> + <string name="disconnect_desktop_question">Desconectar desktop?</string> + <string name="enable_camera_access">Habilitar o acesso à câmera</string> + <string name="encryption_renegotiation_error">Erro de renegociação de criptografia</string> + <string name="unable_to_open_browser_title">Erro ao abrir o navegador</string> + <string name="error_sending_message_contact_invitation">Erro ao enviar o convite</string> + <string name="loading_chats">Carregando bate-papos…</string> + <string name="remote_host_was_disconnected_toast"><![CDATA[Dispositivo Móvel <b>%s</b> foi desconectado]]></string> + <string name="remote_host_error_disconnected"><![CDATA[Dispositivo Móvel <b>%s</b> foi desconectado]]></string> + <string name="only_one_device_can_work_at_the_same_time">Apenas um dispositivo pode funcionar ao mesmo tempo</string> + <string name="rcv_group_event_open_chat">Abrir</string> + <string name="past_member_vName">Ex-membro %1$s</string> + <string name="agent_internal_error_desc">Por favor informe isto aos desenvolvedores: +\n%s</string> + <string name="loading_remote_file_desc">Por favor, espere até que o arquivo seja carregado pelo dispositivo móvel vínculado</string> + <string name="you_can_change_it_later">Senha aleatória é armazenadas nas configurações como um texto simples. +\nVocê pode mudar isso mais tarde.</string> + <string name="send_receipts_disabled_alert_title">Os recibos estão desativados</string> + <string name="remove_passphrase_from_settings">Remover senha das configurações?</string> + <string name="set_database_passphrase">Definir senha do banco de dados</string> + <string name="receipts_section_groups">Grupos pequenos (max 20)</string> + <string name="error_showing_message">erro ao mostrar a mensagem</string> + <string name="error_showing_content">erro ao mostrar o conteúdo</string> + <string name="error_alert_title">Erro</string> + <string name="invalid_name">Nome inválido!</string> + <string name="create_another_profile_button">Criar perfil</string> + <string name="settings_section_title_use_from_desktop">Usar pelo desktop</string> + <string name="v5_4_link_mobile_desktop">Vincule aplicativos móveis e de desktop! 🔗</string> + <string name="v5_4_better_groups">Grupos melhores</string> + <string name="v5_4_block_group_members">Bloquear membros do grupo</string> + <string name="v5_4_incognito_groups_descr">Criar um grupo usando um perfil aleatório.</string> + <string name="v5_4_better_groups_descr">Adesão mais rápida e mensagens mais confiáveis.</string> + <string name="v5_4_incognito_groups">Grupos anônimos</string> + <string name="v5_4_more_things_descr">- opcionalmente notificar contatos deletados +\n- nomes de perfil com espaços +\n- e mais!</string> + <string name="v5_4_block_group_members_descr">Para ocultar mensagens indesejadas.</string> + <string name="v5_4_link_mobile_desktop_descr">Via protocolo seguro de resistência quântica.</string> + <string name="devices">Dispositivos</string> + <string name="disconnect_remote_host">Disconectar</string> + <string name="enter_this_device_name">Digite o nome deste dispositivo…</string> + <string name="new_mobile_device">Novo dispositivo móvel</string> + <string name="session_code">Código de sessão</string> + <string name="this_device_name_shared_with_mobile">O nome do dispositivo será compartilhado com o cliente móvel conectado.</string> + <string name="this_device_name">O nome deste dispositivo</string> + <string name="unlink_desktop">Desvincular</string> + <string name="unlink_desktop_question">Desvincular desktop?</string> + <string name="verify_code_on_mobile">Verificar código no dispositivo móvel</string> + <string name="verify_connection">Verificar conexão</string> + <string name="linked_desktop_options">Opções de desktop vinculada</string> + <string name="scan_qr_code_from_desktop">Escanear QR code pelo desktop</string> + <string name="connect_plan_you_are_already_connecting_to_vName"><![CDATA[Você já está se conectando à <b>%1$s</b>.]]></string> + <string name="group_member_role_author">autor</string> + <string name="recipient_colon_delivery_status">%s: %s</string> + <string name="multicast_connect_automatically">Conectar automaticamente</string> + <string name="desktop_devices">Dispositivos desktop</string> + <string name="multicast_discoverable_via_local_network">Descobrível via rede local</string> + <string name="possible_slow_function_desc">A execução da função está demorando muito: %1$d segundos: %2$s</string> + <string name="possible_slow_function_title">Função lenta</string> + <string name="member_contact_send_direct_message">enviar mensagem direta</string> + <string name="retry_verb">Tentar novamente</string> + <string name="open_database_folder">Abrir a pasta do banco de dados</string> + <string name="terminal_always_visible">Mostrar console em uma nova janela</string> + <string name="profile_update_event_contact_name_changed">contato %1$s mudou para %2$s</string> + <string name="profile_update_event_member_name_changed">membro %1$s mudou para %2$s</string> + <string name="profile_update_event_removed_address">endereço do contato removido</string> + <string name="profile_update_event_removed_picture">foto de perfil removida</string> + <string name="profile_update_event_set_new_address">definir novo endereço de contato</string> + <string name="profile_update_event_set_new_picture">definir nova foto de perfil</string> + <string name="group_member_status_unknown">status desconhecido</string> + <string name="profile_update_event_updated_profile">perfil atualizado</string> + <string name="recent_history">Histórico visível</string> + <string name="disable_sending_recent_history">Não enviar histórico para novos membros.</string> + <string name="v5_3_new_desktop_app_descr">Criar novo perfil no aplicativo de desktop. 💻</string> + <string name="v5_3_encrypt_local_files">Criptografar arquivos armazenados & arquivos de mídia</string> + <string name="v5_3_new_desktop_app">Novo aplicativo de desktop!</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml index 98e6223d87..9d7bfe4ca0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -1713,4 +1713,89 @@ <string name="error_blocking_member_for_all">Ошибка при блокировании члена для всех</string> <string name="unblock_for_all_question">Разблокировать члена для всех?</string> <string name="snd_group_event_member_blocked">Вы заблокировали %s</string> + <string name="e2ee_info_no_pq"><![CDATA[Сообщения, файлы и звонки защищены <b>end-to-end шифрованием</b> с прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома.]]></string> + <string name="e2ee_info_no_pq_short">Чат защищен end-to-end шифрованием.</string> + <string name="e2ee_info_pq_short">Чат защищен квантово-устойчивым end-to-end шифрованием.</string> + <string name="auth_open_migration_to_another_device">Открыть экран миграции</string> + <string name="migrate_from_another_device">Миграция с другого устройства</string> + <string name="set_passphrase">Установить пароль</string> + <string name="conn_event_disabled_pq">стандартное end-to-end шифрование</string> + <string name="welcome_message_is_too_long">Приветственное сообщение слишком длинное</string> + <string name="message_too_large">Сообщение слишком большое</string> + <string name="migrate_to_device_repeat_download">Повторить загрузку</string> + <string name="migrate_to_device_try_again">Вы можете попробовать еще раз.</string> + <string name="migrate_from_device_uploading_archive">Загрузка архива</string> + <string name="migrate_from_device_archiving_database">Подготовка архива</string> + <string name="migrate_from_device_bytes_uploaded">%s загружено</string> + <string name="migrate_from_device_start_chat">Запустить чат</string> + <string name="v5_6_app_data_migration">Миграция данных</string> + <string name="v5_6_quantum_resistant_encryption_descr">Включите для контактов (BETA)!</string> + <string name="v5_6_safer_groups">Более безопасные группы</string> + <string name="v5_6_safer_groups_descr">Админы могут заблокировать члена группы.</string> + <string name="paste_archive_link">Вставьте ссылку архива</string> + <string name="invalid_file_link">Ошибка ссылки</string> + <string name="migrate_to_device_migrating">Миграция</string> + <string name="migrate_to_device_downloading_archive">Загрузка архива</string> + <string name="migrate_to_device_downloading_details">Загрузка ссылки архива</string> + <string name="migrate_to_device_database_init">Подготовка загрузки</string> + <string name="migrate_to_device_download_failed">Ошибка загрузки</string> + <string name="migrate_to_device_bytes_downloaded">%s загружено</string> + <string name="migrate_to_device_importing_archive">Импорт архива</string> + <string name="migrate_to_device_import_failed">Ошибка импорта</string> + <string name="migrate_to_device_chat_migrated">Чат мигрирован!</string> + <string name="migrate_to_device_enter_passphrase">Введите пароль</string> + <string name="migrate_to_device_error_downloading_archive">Ошибка загрузки архива</string> + <string name="migrate_to_device_repeat_import">Повторить импорт</string> + <string name="migrate_to_device_confirm_network_settings_footer">Пожалуйста, подтвердите, что настройки сети верны для этого устройства.</string> + <string name="migrate_to_device_apply_onion">Применить</string> + <string name="migrate_from_device_title">Мигрировать устройство</string> + <string name="migrate_from_device_error_saving_settings">Ошибка сохранения настроек</string> + <string name="migrate_from_device_error_exporting_archive">Ошибка при экспорте архива чата</string> + <string name="migrate_from_device_database_init">Подготовка загрузки</string> + <string name="migrate_from_device_error_uploading_archive">Ошибка загрузки архива</string> + <string name="migrate_from_device_error_deleting_database">Ошибка при удалении данных чата</string> + <string name="migrate_from_device_stopping_chat">Остановка чата</string> + <string name="migrate_from_device_archive_and_upload">Архивировать и загрузить</string> + <string name="migrate_from_device_confirm_upload">Подтвердить загрузку</string> + <string name="migrate_from_device_all_data_will_be_uploaded">Все ваши контакты, разговоры и файлы будут надежно зашифрованы и загружены на выбранные XFTP серверы.</string> + <string name="migrate_from_device_upload_failed">Ошибка загрузки</string> + <string name="migrate_from_device_repeat_upload">Повторить загрузку</string> + <string name="migrate_from_device_try_again">Вы можете попробовать еще раз.</string> + <string name="migrate_from_device_cancel_migration">Отменить миграцию</string> + <string name="migrate_from_device_choose_migrate_from_another_device"><![CDATA[Выберите <i>Мигрировать с другого устройства</i> на новом устройстве и сосканируйте QR код.]]></string> + <string name="migrate_from_device_creating_archive_link">Создание ссылки на архив</string> + <string name="migrate_from_device_delete_database_from_device">Удалить базу данных с этого устройства</string> + <string name="migrate_from_device_finalize_migration">Завершить миграцию</string> + <string name="migrate_from_device_or_share_this_file_link">Или передайте эту ссылку</string> + <string name="migrate_from_device_migration_complete">Миграция завершена</string> + <string name="migrate_from_device_starting_chat_on_multiple_devices_unsupported">Внимание: запуск чата на нескольких устройствах не поддерживается и приведет к сбоям доставки сообщений.</string> + <string name="migrate_from_device_you_must_not_start_database_on_two_device"><![CDATA[Вы <b>не должны</b> использовать одну и ту же базу данных на двух устройствах.]]></string> + <string name="migrate_from_device_check_connection_and_try_again">Проверьте подключение к Интернету и повторите попытку</string> + <string name="migrate_from_device_confirm_you_remember_passphrase">Подтвердите, что Вы помните пароль базы данных для ее миграции.</string> + <string name="migrate_from_device_verify_database_passphrase">Проверка пароля базы данных</string> + <string name="migrate_from_device_verify_passphrase">Проверить пароль</string> + <string name="migrate_from_device_error_verifying_passphrase">Ошибка подтверждения пароля:</string> + <string name="call_service_notification_audio_call">Аудиозвонок</string> + <string name="call_service_notification_end_call">Завершить звонок</string> + <string name="call_service_notification_video_call">Видеозвонок</string> + <string name="migrate_from_device_chat_should_be_stopped">Чтобы продолжить, чат должен быть остановлен.</string> + <string name="migrate_from_device_using_on_two_device_breaks_encryption"><![CDATA[<b>Обратите внимание</b>: использование одной и той же базы данных на двух устройствах нарушит расшифровку сообщений от ваших контактов, как свойство защиты соединений.]]></string> + <string name="migrate_from_device_archive_will_be_deleted"><![CDATA[<b>Внимание</b>: архив будет удален.]]></string> + <string name="migrate_to_device_confirm_network_settings">Подтвердите настройки сети</string> + <string name="e2ee_info_pq"><![CDATA[Сообщения, файлы и звонки защищены <b>квантово-устойчивым end-to-end шифрованием</b> с идеальной прямой секретностью (PFS), правдоподобным отрицанием и восстановлением от взлома.]]></string> + <string name="migrate_to_device_title">Мигрировать сюда</string> + <string name="migrate_from_device_to_another_device">Мигрировать на другое устройство</string> + <string name="v5_6_app_data_migration_descr">Мигрируйте на другое устройство через QR код.</string> + <string name="or_paste_archive_link">Или вставьте ссылку архива</string> + <string name="v5_6_picture_in_picture_calls">Звонки с картинкой-в-картинке</string> + <string name="conn_event_enabled_pq">квантово-устойчивое e2e шифрование</string> + <string name="v5_6_quantum_resistant_encryption">Квантово-устойчивое шифрование</string> + <string name="v5_6_picture_in_picture_calls_descr">Используйте приложение во время звонка.</string> + <string name="migrate_from_device_exported_file_doesnt_exist">Экспортированный файл не существует</string> + <string name="migrate_to_device_file_delete_or_link_invalid">Файл удален или ошибка ссылки</string> + <string name="migrate_to_device_finalize_migration">Завершите миграцию на другом устройстве.</string> + <string name="database_migration_in_progress">Выполняется миграция базы данных. +\nЭто может занять несколько минут.</string> + <string name="unable_to_open_browser_title">Ошибка открытия браузера</string> + <string name="unable_to_open_browser_desc">Для звонков требуется веб-браузер по умолчанию. Пожалуйста, настройте браузер по умолчанию в системе и поделитесь дополнительной информацией с разработчиками.</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index aa66835ee1..4464e323f7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -238,7 +238,7 @@ <string name="icon_descr_call_progress">Arama yapılıyor</string> <string name="icon_descr_call_rejected">Geri çevrilmiş çağrı</string> <string name="icon_descr_call_ended">Görüşme bitti.</string> - <string name="alert_text_decryption_error_too_many_skipped">%1$d mesajlar atlanıldı.</string> + <string name="alert_text_decryption_error_too_many_skipped">%1$d mesaj atlanıldı.</string> <string name="chat_database_deleted">Sohbet veritabanı silindi</string> <string name="error_with_info">Hata: %s</string> <string name="unknown_error">Bilinmeyen hata</string> @@ -1289,7 +1289,7 @@ <string name="stop_snd_file__message">Dosya gönderimi durdurulacaktır.</string> <string name="moderated_description">yönetildi</string> <string name="error_creating_member_contact">Üye bağlantısı oluşturulurken hata</string> - <string name="sending_delivery_receipts_will_be_enabled">Çoklu gönderim bütün kişiler için etkinleştirilecektir.</string> + <string name="sending_delivery_receipts_will_be_enabled">Tüm kişiler için iletim bilgisi gönderme özelliği etkinleştirilecek</string> <string name="ensure_xftp_server_address_are_correct_format_and_unique">XFTP sunucu adreslerinin doğru formatta olduğundan, satırın ayrılmış ve kopyalanmamış olduğundan emin olun.</string> <string name="refresh_qr_code">Yenile</string> <string name="socks_proxy_setting_limitations"><![CDATA[<b>Lütfen unutmayın</b>: mesaj ve dosya yönlendiricileri SOCKS vekili tarafından bağlandı. Aramalar ve bağlantı ön gösterimleri doğrudan bağlantı kullanıyor.]]></string> @@ -1306,7 +1306,7 @@ <string name="receipts_contacts_override_enabled">Alıcılar %d bağlantıları için etkinleştirilecektir</string> <string name="moderate_message_will_be_marked_warning">Mesaj herkes için yönetilmiş olarak işaretlenecek.</string> <string name="connect_plan_this_is_your_own_one_time_link">Bu senin kendi tek-kullanımlık bağlantın!</string> - <string name="marked_deleted_items_description">%d mesajları silinmiş olarak işaretlendi</string> + <string name="marked_deleted_items_description">%d mesaj silinmiş olarak işaretlendi</string> <string name="connect_use_new_incognito_profile">Yeni sahte profil kullan</string> <string name="chat_is_stopped_you_should_transfer_database">Sohbet durduruldu. Eğer çoktan başka bir cihazda bu veritabanını kullandıysan,sohbeti başlatmadan önce veritabanını geri aktarmalısın.</string> <string name="send_link_previews">Bağlantı ön izlemelerini gönder</string> @@ -1359,7 +1359,7 @@ <string name="share_text_sent_at">%s de gönderildi</string> <string name="enable_sending_recent_history">Yeni katılımcılara 100e kadar son mesajlar gönder</string> <string name="moderate_verb">Yönet</string> - <string name="moderated_items_description">%1$d mesajları %2$s tarafından yönetildi</string> + <string name="moderated_items_description">%1$d mesaj %2$s tarafından yönetildi</string> <string name="remove_passphrase_from_settings">Ayarlardaki parola silinsin mi?</string> <string name="receipts_contacts_title_enable">Alıcılar etkinleştirilsin mi?</string> <string name="tap_to_start_new_chat">Yeni bir sohbet başlatmak için tıkla</string> @@ -1429,7 +1429,7 @@ <string name="set_database_passphrase">Veritabanı parolası ayarla</string> <string name="share_this_1_time_link">Bu tek-kullanımlık davet bağlantısını paylaş</string> <string name="unblock_member_question">Kişinin engeli kaldırılsın mı?</string> - <string name="blocked_items_description">%d mesajları engellendi</string> + <string name="blocked_items_description">%d mesaj engellendi</string> <string name="create_group_button_to_create_new_group"><![CDATA[<b>Grup oluştur</b>: yeni bir grup oluşturmak için.]]></string> <string name="icon_descr_video_asked_to_receive">Videoyu almak için soruldu</string> <string name="open_port_in_firewall_desc">Telefon uygulamanın bilgisayarına bağlanmasına izin vermek için, güvenlik duvarın etkin ise bu portu aç</string> @@ -1554,4 +1554,166 @@ <string name="color_received_message">Alınmış mesaj</string> <string name="rcv_group_event_member_created_contact">doğrudan bağlandı</string> <string name="blocked_item_description">engellendi</string> + <string name="v5_5_private_notes">Gizli notlar</string> + <string name="v5_5_message_delivery_descr">Azaltılmış pil kullanımı ile birlikte.</string> + <string name="v5_5_message_delivery">İyileştirilmiş mesaj iletimi</string> + <string name="v5_5_new_interface_languages">Macarca ve Türkçe arayüz</string> + <string name="remote_host_error_timeout"><![CDATA[Telefona bağlanırken zaman aşımına uğranıldı <b>%s</b>]]></string> + <string name="remote_host_error_disconnected"><![CDATA[Telefon <b>%s</b> bağlantısı kesildi]]></string> + <string name="remote_ctrl_error_disconnected">Masaüstünün bağlantısı kesilmiş</string> + <string name="blocked_by_admin_item_description">yönetici tarafından engellendi</string> + <string name="blocked_by_admin_items_description">%d mesaj yönetici tarafından engellendi</string> + <string name="clear_note_folder_question">Gizli notlar temizlensin mi?</string> + <string name="clear_note_folder_warning">Tüm mesajlar silinecektir - bu geri alınamaz!</string> + <string name="developer_options_section">Geliştirici seçenekleri</string> + <string name="show_internal_errors">Dahili hataları göster</string> + <string name="show_slow_api_calls">Yavaş API aramalarını göster</string> + <string name="rcv_group_event_member_blocked">engellendi %s</string> + <string name="rcv_group_event_member_unblocked">engeli kaldırıldı %s</string> + <string name="group_member_status_unknown_short">bilinmeyen</string> + <string name="group_member_status_unknown">bilinmeyen durum</string> + <string name="snd_group_event_member_blocked">engelledin %s</string> + <string name="snd_group_event_member_unblocked">engeli kaldırdın %s</string> + <string name="past_member_vName">Geçmiş üye %1$s</string> + <string name="member_blocked_by_admin">Yönetici tarafından engellendi</string> + <string name="block_for_all">Herkes için engelle</string> + <string name="info_row_created_at">Şurada oluşturuldu</string> + <string name="share_text_created_at">Şurada oluşturuldu: %s</string> + <string name="error_blocking_member_for_all">Üye herkes için engellenirken hata oluştu</string> + <string name="unblock_for_all">Herkes için engelini kaldır</string> + <string name="saved_message_title">Kaydedilmiş mesaj</string> + <string name="remote_host_was_disconnected_title">Bağlantı durduruldu</string> + <string name="remote_host_error_bad_state"><![CDATA[Telefona bağlantı <b>%s</b> kötü durumda]]></string> + <string name="remote_host_disconnected_from"><![CDATA[Telefon bağlantısı <b>%s</b> şu nedenle kesildi: %s]]></string> + <string name="remote_ctrl_disconnected_with_reason">Nedeniyle birlikte bağlantı kesildi: %s</string> + <string name="v5_5_private_notes_descr">Şifrelenmiş dosyalar ve medya ile birlikte.</string> + <string name="v5_5_join_group_conversation">Grup sohbetlerine katıl</string> + <string name="v5_5_simpler_connect_ui">Bağlanmak için bağlantıyı yapıştır!</string> + <string name="remote_ctrl_error_timeout">Masaüstüne bağlanırken zaman aşımına ulaşıldı</string> + <string name="v5_5_join_group_conversation_descr">Yakın tarih ve geliştirilmiş dizin botu.</string> + <string name="v5_5_simpler_connect_ui_descr">Arama çubuğu davet bağlantılarını kabul eder.</string> + <string name="member_info_member_blocked">engellendi</string> + <string name="block_for_all_question">Üye herkes için engellensin mi?</string> + <string name="unblock_for_all_question">Üyenin engeli herkes için kaldırılsın mı?</string> + <string name="remote_host_error_bad_version"><![CDATA[Mobil <b>%s</b> desteklenmeyen bir sürüme sahip. Lütfen her iki cihazda da aynı sürümü kullandığınızdan emin olun]]></string> + <string name="remote_host_error_busy"><![CDATA[Telefon <b>%s</b> meşgul]]></string> + <string name="remote_host_error_inactive"><![CDATA[Telefon <b>%s</b> aktif değil]]></string> + <string name="remote_host_error_missing"><![CDATA[Telefon <b>%s</b> kayıp]]></string> + <string name="remote_ctrl_error_inactive">Masaüstü aktif değil</string> + <string name="remote_ctrl_error_bad_state">Masaüstüne bağlantı kötü durumda</string> + <string name="remote_ctrl_error_busy">Masaüstü meşgul</string> + <string name="remote_ctrl_error_bad_version">Masaüstü desteklenmeyen bir sürüme sahiptir. Lütfen her iki cihazda da aynı sürümü kullandığınızdan emin olun</string> + <string name="agent_critical_error_title">Kritik hata</string> + <string name="remote_ctrl_error_bad_invitation">Masaüstünün yanlış davetiye kodu var</string> + <string name="agent_internal_error_title">Dahili hata</string> + <string name="agent_internal_error_desc">Lütfen geliştiricilere bildir: +\n +\n%s</string> + <string name="agent_critical_error_desc">Lütfen geliştiricilere bildir: +\n +\n%s +\n +\nUygulamayı yeniden başlatmak önerilir.</string> + <string name="restart_chat_button">Sohbeti yeniden başlat</string> + <string name="failed_to_create_user_invalid_desc">Bu görünen ad geçersiz. +\nLütfen başka bir ad seç.</string> + <string name="failed_to_create_user_invalid_title">Geçersiz görünen ad!</string> + <string name="remote_ctrl_was_disconnected_title">Bağlantı durduruldu</string> + <string name="possible_slow_function_desc">İşlevin yürütülmesi çok uzun zaman alıyor: %1$d saniye: %2$s</string> + <string name="possible_slow_function_title">Yavaş fonksiyon</string> + <string name="profile_update_event_contact_name_changed">kişi %1$s olarak değişti %2$s</string> + <string name="profile_update_event_updated_profile">güncellenmiş profil</string> + <string name="profile_update_event_member_name_changed">üye %1$s olarak değişti %2$s</string> + <string name="profile_update_event_set_new_address">yeni kişi adresi ayarla</string> + <string name="profile_update_event_set_new_picture">yeni profil fotoğrafı ayarla</string> + <string name="profile_update_event_removed_address">kişi adresi silindi</string> + <string name="profile_update_event_removed_picture">profil fotoğrafı silindi</string> + <string name="error_creating_message">Mesaj oluşturulurken hata</string> + <string name="error_deleting_note_folder">Gizli notlar silinirken hata</string> + <string name="note_folder_local_display_name">Gizli notlar</string> + <string name="welcome_message_is_too_long">Hoşgeldin mesajı çok uzun</string> + <string name="message_too_large">Mesaj çok büyük</string> + <string name="database_migration_in_progress">Veritabanı geçişi devam ediyor. +\nBu birkaç dakika sürebilir.</string> + <string name="unable_to_open_browser_desc">Aramalar için varsayılan web tarayıcısı gereklidir. Lütfen sistemdeki varsayılan tarayıcıyı yapılandırın ve geliştiricilerle daha fazla bilgi paylaşın.</string> + <string name="call_service_notification_audio_call">Sesli arama</string> + <string name="call_service_notification_video_call">Görüntülü arama</string> + <string name="call_service_notification_end_call">Aramayı bitir</string> + <string name="unable_to_open_browser_title">Tarayıcıyı açarken hata</string> + <string name="e2ee_info_no_pq"><![CDATA[Mesajlar, dosyalar ve aramalar, mükemmel ileriye dönük gizlilik, reddetme ve izinsiz giriş kurtarması ile <b>uçtan uca şifreleme</b> ile korunmaktadır.]]></string> + <string name="e2ee_info_no_pq_short">Bu sohbet uçtan uca şifrelemeyle korunmaktadır.</string> + <string name="auth_open_migration_to_another_device">Taşıma ekranını aç</string> + <string name="migrate_from_another_device">Başka bir cihazdan taşı</string> + <string name="set_passphrase">Parolayı ayarla</string> + <string name="or_paste_archive_link">Veya arşiv bağlantısını yapıştırın</string> + <string name="migrate_to_device_chat_migrated">Sohbet taşındı!</string> + <string name="migrate_to_device_apply_onion">Uygula</string> + <string name="migrate_from_device_finalize_migration">Taşıma işlemini sonlandır</string> + <string name="v5_6_quantum_resistant_encryption_descr">Doğrudan sohbetlerde etkinleştir (BETA)!</string> + <string name="v5_6_quantum_resistant_encryption">Kuantuma dayanıklı şifreleme</string> + <string name="v5_6_safer_groups_descr">Yöneticiler bir üyeyi tamamen engelleyebilirler.</string> + <string name="v5_6_app_data_migration">Uygulama veri göçü.</string> + <string name="v5_6_app_data_migration_descr">QR kodu aracılığıyla başka bir cihaza geçiş yapın.</string> + <string name="v5_6_picture_in_picture_calls">Resim içinde resim çağrıları</string> + <string name="v5_6_safer_groups">Daha güvenli gruplar</string> + <string name="v5_6_picture_in_picture_calls_descr">Arama sırasında uygulamayı kullanın.</string> + <string name="migrate_to_device_title">Buraya taşı</string> + <string name="paste_archive_link">Arşiv bağlantısını yapıştır</string> + <string name="migrate_to_device_database_init">İndirmeye hazırlanıyor</string> + <string name="migrate_to_device_download_failed">Yükleme başarısız</string> + <string name="migrate_to_device_downloading_archive">Arşiv indiriliyor</string> + <string name="migrate_to_device_downloading_details">Bağlantı detayları indiriliyor</string> + <string name="migrate_to_device_repeat_download">İndirmeyi tekrarla</string> + <string name="migrate_to_device_bytes_downloaded">%s indirildi</string> + <string name="migrate_to_device_import_failed">İçe aktarma başarısız oldu</string> + <string name="migrate_to_device_importing_archive">Arşiv içe aktarılıyor</string> + <string name="migrate_to_device_repeat_import">İçe aktarmayı tekrarla</string> + <string name="migrate_to_device_try_again">Bir kez daha deneyebilirsiniz.</string> + <string name="migrate_to_device_error_downloading_archive">Arşiv indirilirken hata oluştu</string> + <string name="migrate_to_device_finalize_migration">Taşıma işlemini başka bir cihazda sonlandırın.</string> + <string name="migrate_from_device_exported_file_doesnt_exist">Dışa aktarılan dosya mevcut değil</string> + <string name="migrate_from_device_title">Cihazı taşı</string> + <string name="migrate_from_device_to_another_device">Başka bir cihaza geçiş</string> + <string name="migrate_from_device_error_deleting_database">Veritabanı silinirken hata oluştu</string> + <string name="migrate_from_device_error_exporting_archive">Sohbet veritabanı dışa aktarılırken hata oluştu</string> + <string name="migrate_from_device_error_uploading_archive">Arşiv yüklenirken hata oluştu</string> + <string name="migrate_from_device_database_init">Yükleme hazırlanıyor</string> + <string name="migrate_from_device_confirm_upload">Yüklemeyi onayla</string> + <string name="migrate_from_device_chat_should_be_stopped">Devam etmek için sohbetin durdurulması gerekiyor.</string> + <string name="migrate_from_device_archiving_database">Veritabanını arşivleme</string> + <string name="migrate_from_device_repeat_upload">Yüklemeyi tekrarla</string> + <string name="migrate_from_device_bytes_uploaded">%s yüklendi</string> + <string name="migrate_from_device_upload_failed">Yükleme başarısız</string> + <string name="migrate_from_device_uploading_archive">Arşiv yükleniyor</string> + <string name="migrate_from_device_using_on_two_device_breaks_encryption"><![CDATA[<b>Lütfen dikkat</b>: aynı veritabanını iki cihazda kullanmak, bir güvenlik koruması olarak bağlantılarınızdaki mesajların şifresinin çözülmesini engelleyecektir.]]></string> + <string name="migrate_from_device_cancel_migration">Taşımayı iptal et</string> + <string name="migrate_from_device_creating_archive_link">Arşiv bağlantısı oluşturuluyor</string> + <string name="migrate_from_device_migration_complete">Taşıma işlemi tamamlandı</string> + <string name="migrate_from_device_or_share_this_file_link">Veya bu dosya bağlantısını güvenli bir şekilde paylaşın</string> + <string name="migrate_from_device_start_chat">Sohbeti başlat</string> + <string name="migrate_from_device_starting_chat_on_multiple_devices_unsupported">Uyarı: Birden fazla cihazda sohbet başlatmak desteklenmez ve mesaj iletimi başarısızlıklara neden olabilir.</string> + <string name="migrate_from_device_verify_database_passphrase">Veritabanı parolasını doğrulayın</string> + <string name="migrate_from_device_verify_passphrase">Parolayı doğrulayın</string> + <string name="migrate_from_device_all_data_will_be_uploaded">Tüm kişileriniz, konuşmalarınız ve dosyalarınız güvenli bir şekilde şifrelenir ve yapılandırılmış XFTP rölelerine parçalar halinde yüklenir.</string> + <string name="migrate_from_device_archive_and_upload">Arşivle ve yükle</string> + <string name="migrate_from_device_archive_will_be_deleted"><![CDATA[<b>Uyarı</b>: arşiv silinecektir.]]></string> + <string name="migrate_from_device_confirm_you_remember_passphrase">Taşımak için veritabanı parolasını hatırladığınızı doğrulayın.</string> + <string name="migrate_from_device_check_connection_and_try_again">İnternet bağlantınızı kontrol edip tekrar deneyin</string> + <string name="migrate_to_device_confirm_network_settings">Ağ ayarlarını onaylayın</string> + <string name="migrate_from_device_delete_database_from_device">Veritabanını bu cihazdan sil</string> + <string name="migrate_to_device_enter_passphrase">Parolayı girin</string> + <string name="migrate_from_device_error_saving_settings">Ayarlar kaydedilirken hata oluştu</string> + <string name="migrate_from_device_error_verifying_passphrase">Parola doğrulanırken hata oluştu:</string> + <string name="migrate_to_device_file_delete_or_link_invalid">Dosya silindi veya bağlantı geçersiz</string> + <string name="invalid_file_link">Geçersiz link</string> + <string name="e2ee_info_pq"><![CDATA[Mesajlar, dosyalar ve aramalar, mükemmel iletme gizliliği, reddetme ve izinsiz giriş kurtarma özellikleriyle <b>kuantum dirençli e2e şifreleme</b> ile korunmaktadır.]]></string> + <string name="migrate_to_device_migrating">Taşınıyor</string> + <string name="migrate_to_device_confirm_network_settings_footer">Lütfen bu cihaz için ağ ayarlarının doğru olduğunu onaylayın.</string> + <string name="conn_event_enabled_pq">kuantum dirençli e2e şifreleme</string> + <string name="conn_event_disabled_pq">standart uçtan uca şifreleme</string> + <string name="migrate_from_device_stopping_chat">Sohbet durduruluyor</string> + <string name="e2ee_info_pq_short">Bu sohbet kuantum dirençli uçtan uca şifrelemeyle korunmaktadır.</string> + <string name="migrate_from_device_you_must_not_start_database_on_two_device"><![CDATA[Aynı veritabanını iki cihazda <b>kullanmamalısınız</b>.]]></string> + <string name="migrate_from_device_try_again">Bir kez daha deneyebilirsiniz.</string> + <string name="migrate_from_device_choose_migrate_from_another_device"><![CDATA[Yeni cihazda <i>Başka bir cihazdan taşı</i>\'yı seçin ve QR kodunu tarayın.]]></string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml index 34ac98e401..8aebe017ec 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -20,7 +20,7 @@ <string name="v4_3_improved_server_configuration_desc">Додавайте сервери, скануючи QR-коди.</string> <string name="users_delete_all_chats_deleted">Всі чати і повідомлення будуть видалені - цю дію неможливо скасувати!</string> <string name="allow_calls_only_if">Дозволяйте дзвінки тільки в разі дозволу вашого контакту.</string> - <string name="allow_irreversible_message_deletion_only_if">Дозволяйте невідворотне видалення повідомлень тільки в разі дозволу вашого контакту.</string> + <string name="allow_irreversible_message_deletion_only_if">Дозволити безповоротне видалення повідомлень, тільки якщо ваш контакт дозволяє вам. (24 години)</string> <string name="allow_voice_messages_question">Дозволити голосові повідомлення\?</string> <string name="app_passcode_replaced_with_self_destruct">Пароль застосунку замінено паролем самознищення.</string> <string name="smp_servers_preset_add">Додати попередньо встановлені сервери</string> @@ -35,7 +35,7 @@ <string name="always_use_relay">Завжди використовувати реле</string> <string name="settings_section_title_app">ДОДАТОК</string> <string name="allow_direct_messages">Дозволяйте надсилати прямі повідомлення учасникам.</string> - <string name="allow_to_delete_messages">Дозволяйте невідворотне видалення відправлених повідомлень.</string> + <string name="allow_to_delete_messages">Дозволити безповоротно видаляти надіслані повідомлення. (24 години)</string> <string name="allow_to_send_voice">Дозволяйте надсилати голосові повідомлення.</string> <string name="allow_message_reactions">Дозволити реакції на повідомлення.</string> <string name="v5_1_self_destruct_passcode_descr">Вся інформація стирається при його введенні.</string> @@ -46,7 +46,7 @@ <string name="allow_message_reactions_only_if">Дозволяйте реакції на повідомлення тільки в разі дозволу вашого контакту.</string> <string name="empty_chat_profile_is_created">Створений порожній профіль чату з наданим ім\'ям, і застосунок відкривається, як завжди.</string> <string name="color_primary_variant">Додатковий акцент</string> - <string name="allow_your_contacts_irreversibly_delete">Дозвольте вашим контактам невідворотно видаляти відправлені повідомлення.</string> + <string name="allow_your_contacts_irreversibly_delete">Дозволити вашим контактам безповоротно видаляти надіслані повідомлення. (24 години)</string> <string name="allow_verb">Дозволити</string> <string name="network_settings">Розширені налаштування мережі</string> <string name="network_enable_socks_info">Отримувати доступ до серверів через SOCKS-проксі на порті %d? Проксі має бути запущено до активації цієї опції.</string> @@ -87,7 +87,7 @@ <string name="icon_descr_video_asked_to_receive">Запит на отримання відео</string> <string name="v4_2_auto_accept_contact_requests">Автоприйняття запитів на контакт</string> <string name="audio_call_no_encryption">аудіовиклик (не зашифрований e2e)</string> - <string name="both_you_and_your_contacts_can_delete">Як ви, так і ваш контакт можете невідворотно видаляти відправлені повідомлення.</string> + <string name="both_you_and_your_contacts_can_delete">Як і ви, так і ваш контакт можете безповоротно видаляти надіслані повідомлення. (24 години)</string> <string name="alert_title_msg_bad_hash">Поганий хеш повідомлення</string> <string name="alert_title_msg_bad_id">Неправильний ідентифікатор повідомлення</string> <string name="auto_accept_images">Автоматично приймати зображення</string> @@ -497,9 +497,9 @@ <string name="user_mute">Приглушити</string> <string name="enter_password_to_show">Введіть пароль для пошуку</string> <string name="both_you_and_your_contact_can_send_disappearing">Як ви, так і ваш контакт можуть надсилати повідомлення, які зникають.</string> - <string name="only_you_can_delete_messages">Тільки ви можете невідворотно видаляти повідомлення (ваш контакт може позначати їх для видалення).</string> + <string name="only_you_can_delete_messages">Тільки ви можете безповоротно видаляти повідомлення (ваш контакт може позначати їх для видалення). (24 години)</string> <string name="only_your_contact_can_add_message_reactions">Тільки ваш контакт може додавати реакції на повідомлення.</string> - <string name="only_your_contact_can_delete">Тільки ваш контакт може невідворотно видаляти повідомлення (ви можете позначати їх для видалення).</string> + <string name="only_your_contact_can_delete">Тільки ваш контакт може безповоротно видаляти повідомлення (ви можете позначати їх для видалення). (24 години)</string> <string name="only_your_contact_can_send_voice">Тільки ваш контакт може надсилати голосові повідомлення.</string> <string name="prohibit_sending_disappearing">Забороняйте надсилання повідомлень, які зникають.</string> <string name="prohibit_message_deletion">Забороняйте невідворотне видалення повідомлень.</string> @@ -649,7 +649,7 @@ <string name="you_can_create_it_later">Ви можете створити його пізніше</string> <string name="your_current_profile">Ваш поточний профіль</string> <string name="delete_image">Видалити зображення</string> - <string name="save_preferences_question">Зберегти налаштування\?</string> + <string name="save_preferences_question">Зберегти уподобання?</string> <string name="save_and_notify_contact">Зберегти і повідомити контакт</string> <string name="save_profile_password">Зберегти пароль профілю</string> <string name="hidden_profile_password">Пароль схованого профілю</string> @@ -761,7 +761,7 @@ <string name="disappearing_messages_are_prohibited">Самознищувальні повідомлення заборонені в цій групі.</string> <string name="group_members_can_send_dms">Учасники групи можуть надсилати приватні повідомлення.</string> <string name="direct_messages_are_prohibited_in_chat">Приватні повідомлення між учасниками заборонені в цій групі.</string> - <string name="group_members_can_delete">Учасники групи можуть назавжди видаляти відправлені повідомлення.</string> + <string name="group_members_can_delete">Учасники групи можуть назавжди видаляти відправлені повідомлення. (24 години)</string> <string name="message_deletion_prohibited_in_chat">Назавжди видалення повідомлень заборонене в цій групі.</string> <string name="voice_messages_are_prohibited">Голосові повідомлення заборонені в цій групі.</string> <string name="group_members_can_add_message_reactions">Учасники групи можуть додавати реакції на повідомлення.</string> @@ -1370,7 +1370,7 @@ <string name="connect_plan_repeat_connection_request">Повторити запит на підключення?</string> <string name="encryption_renegotiation_error">Помилка переговорів щодо шифрування</string> <string name="rcv_direct_event_contact_deleted">видалено контакт</string> - <string name="connect_plan_you_are_already_connecting_to_vName">Ви вже підключаєтеся до %1$s.</string> + <string name="connect_plan_you_are_already_connecting_to_vName"><![CDATA[Ви вже підключаєтеся до <b>%1$s</b>.]]></string> <string name="rcv_group_event_open_chat">Відкрити</string> <string name="v5_3_encrypt_local_files">Шифрування збережених файлів та медіа</string> <string name="error_alert_title">Помилка</string> @@ -1381,7 +1381,7 @@ <string name="create_another_profile_button">Створити профіль</string> <string name="group_members_2">%s і %s</string> <string name="connect_plan_join_your_group">Приєднатися до вашої групи?</string> - <string name="connect_plan_you_are_already_joining_the_group_vName">Ви вже приєднуєтеся до групи %1$s.</string> + <string name="connect_plan_you_are_already_joining_the_group_vName"><![CDATA[Ви вже приєднуєтеся до групи <b>%1$s</b>.]]></string> <string name="encrypt_local_files">Шифрувати локальні файли</string> <string name="connect_plan_this_is_your_own_one_time_link">Це ваш власний одноразовий посилання!</string> <string name="marked_deleted_items_description">%d повідомлень відзначено як видалені</string> @@ -1410,7 +1410,7 @@ <string name="v5_3_simpler_incognito_mode">Спрощений режим інкогніто</string> <string name="contact_tap_to_connect">Торкніться, щоб підключитися</string> <string name="setup_database_passphrase">Налаштування паролю бази даних</string> - <string name="connect_plan_you_are_already_in_group_vName">Ви вже в групі %1$s.</string> + <string name="connect_plan_you_are_already_in_group_vName"><![CDATA[Ви вже є учасником групи <b>%1$s</b>.]]></string> <string name="connect_plan_this_is_your_own_simplex_address">Це ваш власний адреса SimpleX!</string> <string name="alert_text_encryption_renegotiation_failed">Не вдалося виконати переговори щодо шифрування.</string> <string name="correct_name_to">Виправити ім\'я на %s?</string> @@ -1513,4 +1513,128 @@ <string name="loading_remote_file_desc">Будь ласка, зачекайте, поки файл завантажується з підключеного мобільного</string> <string name="desktop_app_version_is_incompatible">Версія робочого столу %s не сумісна з цим додатком.</string> <string name="verify_connection">Перевірити підключення</string> + <string name="chat_is_stopped_you_should_transfer_database">Чат зупинено. Якщо ви вже використовували цю базу даних на іншому пристрої, перенесіть її назад перед запуском чату.</string> + <string name="v5_5_private_notes_descr">З зашифрованими файлами та медіа.</string> + <string name="v5_5_message_delivery_descr">З меншим споживанням заряду акумулятора.</string> + <string name="agent_critical_error_title">Критична помилка</string> + <string name="v5_5_message_delivery">Покращено доставку повідомлень</string> + <string name="v5_5_join_group_conversation">Приєднатися до групових розмов</string> + <string name="v5_5_simpler_connect_ui">Вставте посилання, щоб приєднатися!</string> + <string name="note_folder_local_display_name">Приватні нотатки</string> + <string name="retry_verb">Повторити</string> + <string name="v5_5_private_notes">Приватні нотатки</string> + <string name="v5_5_simpler_connect_ui_descr">Рядок пошуку підтримує посилання-запрошення.</string> + <string name="search_or_paste_simplex_link">Пошук або вставка посилання SimpleX</string> + <string name="share_this_1_time_link">Поділитися цим одноразовим посиланням-запрошенням</string> + <string name="remote_host_error_timeout"><![CDATA[Перевищено максимальний час очікування з\'єднання з мобільним <b>%s</b>]]></string> + <string name="disconnect_remote_hosts">Відʼєднати мобільні</string> + <string name="add_contact_tab">Додати контакт</string> + <string name="new_chat">Новий чат</string> + <string name="or_show_this_qr_code">Або показати цей код</string> + <string name="invalid_qr_code">Помилка QR-коду</string> + <string name="code_you_scanned_is_not_simplex_link_qr_code">Відсканований код не є QR-кодом посилання SimpleX.</string> + <string name="recent_history">Видима історія повідомлень</string> + <string name="no_connected_mobile">Немає під\'єднаних мобільних</string> + <string name="blocked_by_admin_item_description">заблокований адміністратором</string> + <string name="blocked_by_admin_items_description">%d повідомлень заблоковано адміністратором</string> + <string name="error_creating_message">Помилка при створенні повідомлення</string> + <string name="failed_to_create_user_invalid_desc">Це ім\'я недійсне. Будь ласка, виберіть інше ім\'я.</string> + <string name="error_deleting_note_folder">Помилка при видаленні приватних нотаток</string> + <string name="loading_chats">Завантаження чатів…</string> + <string name="enable_camera_access">Надати доступ до камери</string> + <string name="camera_not_available">Камера недоступна</string> + <string name="tap_to_scan">Торкнутися, щоб відсканувати</string> + <string name="add_contact_button_to_create_link_or_connect_via_link"><![CDATA[<b>Додати контакт</b>: створити нове посилання-запрошення або підключитися за отриманим посиланням.]]></string> + <string name="create_group_button_to_create_new_group"><![CDATA[<b>Створити групу</b>: створити нову групу.]]></string> + <string name="clear_note_folder_question">Очистити приватні нотатки?</string> + <string name="clear_note_folder_warning">Усі повідомлення будуть видалені - цю дію не можна скасувати!</string> + <string name="keep_invitation_link">Залишити</string> + <string name="keep_unused_invitation_question">Залишити невикористане запрошення?</string> + <string name="you_can_view_invitation_link_again">Ви можете знову побачити посилання-запрошення відкривши з\'єднання.</string> + <string name="create_chat_profile">Створити профіль чату</string> + <string name="creating_link">Створення посилання…</string> + <string name="developer_options_section">Налаштування для розробників</string> + <string name="show_slow_api_calls">Показувати повільні виклики API</string> + <string name="you_can_make_address_visible_via_settings">Ви можете зробити це видимим для ваших контактів у SimpleX через Налаштування.</string> + <string name="rcv_group_event_member_blocked">%s заблокований</string> + <string name="rcv_group_event_member_unblocked">%s розблокований</string> + <string name="snd_group_event_member_blocked">ви заблокували %s</string> + <string name="snd_group_event_member_unblocked">ви розблокували %s</string> + <string name="group_member_status_unknown_short">невідомо</string> + <string name="info_row_created_at">Створено</string> + <string name="member_info_member_blocked">заблокований</string> + <string name="member_blocked_by_admin">Заблокований адміністратором</string> + <string name="error_blocking_member_for_all">Помилка під час блокування учасника для всіх</string> + <string name="unblock_for_all">Розблокувати для всіх</string> + <string name="unblock_for_all_question">Розблокувати учасника для всіх?</string> + <string name="disable_sending_recent_history">Не надсилати історію новим користувачам.</string> + <string name="agent_critical_error_desc">Будь ласка, повідомте про це розробників: +\n%s +\n +\nРекомендується перезапустити додаток.</string> + <string name="enable_sending_recent_history">Надсилати до 100 останніх повідомлень новим користувачам.</string> + <string name="recent_history_is_sent_to_new_members">До 100 останніх повідомлень надсилаються новим членам.</string> + <string name="remote_host_was_disconnected_title">З\'єднання перервано</string> + <string name="share_text_created_at">Створено: %s</string> + <string name="show_internal_errors">Показати внутрішні помилки</string> + <string name="agent_internal_error_desc">Будь ласка, повідомте про це розробників: +\n%s</string> + <string name="remote_ctrl_disconnected_with_reason">Відʼєднаний з причини: %s</string> + <string name="remote_ctrl_error_bad_state">Помилка з\'єднання з комп\'ютером</string> + <string name="remote_ctrl_error_bad_invitation">Неправильний код запрошення в комп\'ютера</string> + <string name="remote_ctrl_error_busy">Компʼютер зайнятий</string> + <string name="remote_ctrl_error_inactive">Компʼютер неактивний</string> + <string name="remote_ctrl_error_disconnected">Компʼютер відʼєднаний</string> + <string name="remote_ctrl_error_timeout">Перевищено максимальний час очікування з\'єднання з комп\'ютером.</string> + <string name="remote_host_error_disconnected"><![CDATA[Мобільний <b>%s</b> було відʼєднано]]></string> + <string name="failed_to_create_user_invalid_title">Помилка імені!</string> + <string name="start_chat_question">Почати чат?</string> + <string name="remote_host_disconnected_from"><![CDATA[Від\'єднаний від мобільного <b>%s</b> з причини: %s]]></string> + <string name="remote_host_error_bad_version"><![CDATA[Версія програми на мобільному <b>%s</b> не підтримується. Будь ласка, встановіть однакову версію на обидва пристрої.]]></string> + <string name="agent_internal_error_title">Внутрішня помилка</string> + <string name="restart_chat_button">Оновити чат</string> + <string name="app_was_crashed">Помилка додатку</string> + <string name="refresh_qr_code">Оновити</string> + <string name="database_migration_in_progress">Триває міграція бази даних. +\nЦе може тривати кілька хвилин.</string> + <string name="tap_to_paste_link">Торкнутися, щоб вставити посилання</string> + <string name="the_text_you_pasted_is_not_a_link">Вставлений текст не є посиланням SimpleX.</string> + <string name="saved_message_title">Збережене повідомлення</string> + <string name="block_for_all">Заблокувати для всіх</string> + <string name="block_for_all_question">Заблокувати учасника для всіх?</string> + <string name="message_too_large">Повідомлення надто велике</string> + <string name="welcome_message_is_too_long">Привітання занадто довге</string> + <string name="recent_history_is_not_sent_to_new_members">Історія не надсилається новим учасникам.</string> + <string name="v5_5_new_interface_languages">Угорська та турецька мови інтерфейсу</string> + <string name="v5_5_join_group_conversation_descr">Нещодавня історія повідомлень та покращений бот каталогу.</string> + <string name="remote_ctrl_was_disconnected_title">З\'єднання перервано</string> + <string name="remote_ctrl_error_bad_version">Версія програми на комп\'ютері не підтримується. Будь ласка, встановіть однакову версію на обидва пристрої.</string> + <string name="random_port">Випадковий</string> + <string name="error_showing_message">помилка відображення повідомлення</string> + <string name="error_showing_content">помилка відображення вмісту</string> + <string name="waiting_for_mobile_to_connect">Очікується підключення мобільного:</string> + <string name="open_port_in_firewall_title">Відкрити порт у брандмауері</string> + <string name="open_port_in_firewall_desc">Щоб дозволити мобільному додатку підключатися до комп\'ютера, відкрийте цей порт у брандмауері, якщо він увімкнений</string> + <string name="remote_host_error_missing"><![CDATA[Мобільний <b>%s</b> відсутній]]></string> + <string name="remote_host_error_busy"><![CDATA[Мобільний <b>%s</b> зайнятий]]></string> + <string name="remote_host_error_bad_state"><![CDATA[Помилка з\'єднання з мобільним <b>%s</b>]]></string> + <string name="remote_host_error_inactive"><![CDATA[Мобільний <b>%s</b> неактивний]]></string> + <string name="la_app_passcode">Код для доступу в додаток</string> + <string name="group_member_status_unknown">невідомий статус</string> + <string name="possible_slow_function_desc">Виконання функції займає занадто багато часу: %1$d секунд: %2$s</string> + <string name="possible_slow_function_title">Уповільнене функціонування</string> + <string name="or_scan_qr_code">Або відсканувати QR-код</string> + <string name="profile_update_event_contact_name_changed">контакт %1$s змінено на %2$s</string> + <string name="profile_update_event_member_name_changed">учасник %1$s змінений на %2$s</string> + <string name="profile_update_event_removed_picture">вилучено зображення профілю</string> + <string name="profile_update_event_set_new_address">Установлено нову адресу контакту</string> + <string name="profile_update_event_set_new_picture">Установлено нове зображення профілю</string> + <string name="profile_update_event_updated_profile">оновлений профіль</string> + <string name="profile_update_event_removed_address">вилучено адресу контакту</string> + <string name="past_member_vName">Колишній учасник %1$s</string> + <string name="call_service_notification_end_call">Кінець дзвінка</string> + <string name="call_service_notification_video_call">Відеодзвінок</string> + <string name="call_service_notification_audio_call">Аудіодзвінок</string> + <string name="unable_to_open_browser_title">Помилка відкриття браузера</string> + <string name="unable_to_open_browser_desc">Для використання дзвінків потрібен браузер за замовчуванням. Будь ласка, налаштуйте браузер за замовчуванням в системі та надайте більше інформації розробникам.</string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml new file mode 100644 index 0000000000..693a261be1 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -0,0 +1,88 @@ +<?xml version="1.0" encoding="utf-8"?> +<resources> + <string name="alert_text_decryption_error_n_messages_failed_to_decrypt">%1$d tin nhắn không thể giải mã.</string> + <string name="moderated_items_description">%1$d tin nhắn đã bị xóa bởi %2$s</string> + <string name="group_info_section_title_num_members">%1$s THÀNH VIÊN</string> + <string name="learn_more_about_address">Thông tin về địa chỉ SimpleX</string> + <string name="address_section_title">Địa chỉ</string> + <string name="abort_switch_receiving_address_confirm">Hủy bỏ</string> + <string name="accept_contact_button">Chấp nhận</string> + <string name="one_time_link_short">liên kết dùng một lần</string> + <string name="add_contact_tab">Thêm liên hệ</string> + <string name="about_simplex_chat">Thông tin về SimpleX Chat</string> + <string name="smp_servers_add">Thêm máy chủ…</string> + <string name="about_simplex">Thông tin về SimpleX</string> + <string name="group_member_role_admin">quản trị viên</string> + <string name="button_add_welcome_message">Thêm lời chào</string> + <string name="contact_wants_to_connect_via_call">%1$s muốn kết nối với bạn qua</string> + <string name="accept">Chấp nhận</string> + <string name="alert_text_decryption_error_too_many_skipped">%1$d tin nhắn đã bị bỏ qua.</string> + <string name="users_add">Thêm hồ sơ</string> + <string name="integrity_msg_skipped">%1$d tin nhắn bị bỏ qua</string> + <string name="send_disappearing_message_1_minute">1 phút</string> + <string name="send_disappearing_message_5_minutes">5 phút</string> + <string name="accept_feature">Chấp nhận</string> + <string name="v4_3_improved_server_configuration_desc">Thêm máy chủ bằng cách quét mã QR.</string> + <string name="network_settings">Cài đặt mạng nâng cao</string> + <string name="users_delete_all_chats_deleted">Tất cả các cuộc hội thoại và tin nhắn sẽ bị xóa - quá trình này không thể được hoàn tác!</string> + <string name="abort_switch_receiving_address_question">Hủy bỏ việc đổi địa chỉ?</string> + <string name="abort_switch_receiving_address_desc">Việc thay đổi địa chỉ sẽ bị hủy bỏ. Địa chỉ nhận cũ tiếp tục được sử dụng.</string> + <string name="send_disappearing_message_30_seconds">30 giây</string> + <string name="accept_connection_request__question">Chấp nhận yêu cầu kết nối?</string> + <string name="accept_contact_incognito_button">Chấp nhận bằng hồ sơ ẩn danh</string> + <string name="smp_servers_preset_add">Thêm các máy chủ được cài sẵn</string> + <string name="smp_servers_add_to_another_device">Thêm vào một thiết bị khác</string> + <string name="callstatus_accepted">cuộc gọi được chấp nhận</string> + <string name="conn_event_ratchet_sync_started">đồng ý mã hóa…</string> + <string name="snd_conn_event_ratchet_sync_started">đồng ý mã hóa cho %s</string> + <string name="v5_2_more_things">Một vài điều nữa</string> + <string name="accept_call_on_lock_screen">Chấp nhận</string> + <string name="all_app_data_will_be_cleared">Tất cả dữ liệu đã bị xóa.</string> + <string name="chat_item_ttl_month">1 tháng</string> + <string name="chat_item_ttl_week">1 tuần</string> + <string name="abort_switch_receiving_address">Hủy bỏ việc đổi địa chỉ</string> + <string name="v4_2_group_links_desc">Các quản trị viên có thể tạo liên kết để tham gia nhóm.</string> + <string name="chat_item_ttl_day">Một ngày</string> + <string name="color_secondary_variant">Biến thể của màu thứ cấp</string> + <string name="all_group_members_will_remain_connected">Tất cả các thành viên trong nhóm vẫn sẽ được giữ kết nối.</string> + <string name="clear_note_folder_warning">Tất cả tin nhắn sẽ bị xóa - Quá trình này không thể hoàn tác được!</string> + <string name="block_member_desc">Tất cả các tin nhắn mới từ %s sẽ bị ẩn!</string> + <string name="turn_off_battery_optimization_button">Cho phép</string> + <string name="color_primary">Màu sơ cấp</string> + <string name="network_enable_socks_info">Truy cập các máy chủ thông qua SOCKS proxy tại cổng %d? Proxy phải được khởi động trước khi bật cài đặt này.</string> + <string name="add_address_to_your_profile">Thêm địa chỉ vào hồ sơ để các liên hệ của bạn có thể dễ dàng chia sẻ với mọi người. Bản cập nhật hồ sơ cũng sẽ được gửi tới các liên hệ hiện thời.</string> + <string name="allow_disappearing_messages_only_if">Cho phép nhắn tin nhắn tự xóa chỉ khi liên hệ của bạn cũng cho phép</string> + <string name="allow_verb">Cho phép</string> + <string name="above_then_preposition_continuation">theo như ở trên, thì:</string> + <string name="clear_chat_warning">Tất cả tin nhắn sẽ bị xóa - Quá trình này không thể hoàn tác được! Tin nhắn sẽ CHỈ bị xóa ở phía bạn.</string> + <string name="color_primary_variant">Biến thể của màu sơ cấp</string> + <string name="color_secondary">Màu thứ cấp</string> + <string name="allow_calls_only_if">Cho phép gọi điện chỉ khi liên hệ của bạn cũng cho phép.</string> + <string name="v5_3_new_interface_languages">6 ngôn ngữ giao diện mới</string> + <string name="v5_1_self_destruct_passcode_descr">Tất cả dữ liệu sẽ bị xóa khi nó được nhập vào.</string> + <string name="allow_voice_messages_question">Cho phép tin nhắn thoại?</string> + <string name="allow_irreversible_message_deletion_only_if">Cho phép xóa tin nhắn theo cách không thể phục hồi chỉ khi liên hệ của bạn cũng cho phép điều đó. (24 giờ)</string> + <string name="allow_your_contacts_irreversibly_delete">Cho phép các liên hệ của bạn xóa tin nhắn đã gửi theo cách không thể phục hồi. (24 giờ)</string> + <string name="allow_your_contacts_to_send_disappearing_messages">Cho phép các liên hệ của bạn gửi tin nhắn tự xóa.</string> + <string name="allow_message_reactions_only_if">Cho phép thả biểu tượng cảm xúc chỉ khi liên hệ của bạn cũng cho phép.</string> + <string name="allow_voice_messages_only_if">Cho phép tin nhắn thoại chỉ khi liên hệ của bạn cũng cho phép.</string> + <string name="allow_your_contacts_adding_message_reactions">Cho phép liên hệ của bạn thả biểu tượng cảm xúc.</string> + <string name="allow_your_contacts_to_call">Cho phép các liên hệ của bạn gọi cho bạn.</string> + <string name="allow_your_contacts_to_send_voice_messages">Cho phép các liên hệ của bạn gửi tin nhắn thoại.</string> + <string name="allow_direct_messages">Cho phép gửi tin nhắn trực tiếp tới các thành viên.</string> + <string name="allow_to_delete_messages">Cho phép xóa tin nhắn đã gửi một cách không thể phục hồi. (24 giờ)</string> + <string name="allow_to_send_disappearing">Cho phép gửi tin nhắn tự xóa.</string> + <string name="allow_to_send_voice">Cho phép gửi tin nhắn thoại.</string> + <string name="allow_message_reactions">Cho phép thả biểu tượng cảm xúc.</string> + <string name="allow_to_send_files">Cho phép gửi các tập tin, ảnh và video.</string> + <string name="notifications_mode_service">Luôn luôn bật</string> + <string name="all_your_contacts_will_remain_connected">Tất cả liên hệ của bạn vẫn sẽ được giữ kết nối.</string> + <string name="all_your_contacts_will_remain_connected_update_sent">Tất cả liên hệ của bạn vẫn sẽ được giữ kết nối. Bản cập nhật hồ sơ sẽ được gửi tới các liên hệ của bạn.</string> + <string name="chat_preferences_always">luôn luôn</string> + <string name="connect_plan_already_connecting">Đã kết nối rồi!</string> + <string name="connect_plan_already_joining_the_group">Đã tham gia nhóm rồi!</string> + <string name="always_use_relay">Luôn sử dụng relay</string> + <string name="rcv_group_and_other_events">và %d sự kiện khác</string> + <string name="keychain_is_storing_securely">Android Keystore được sử dụng để lưu trữ passphrase - nó cho phép dịch vụ thông báo hoạt động.</string> + <string name="keychain_allows_to_receive_ntfs">Android Keystore sẽ được sử dụng để lưu trữ passphrase một cách an toàn sau khi bạn khởi động lại ứng dụng hoặc thay đổi passphrase - nó cho phép tiếp nhận thông báo.</string> +</resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml index 6075de500b..3298da5097 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rCN/strings.xml @@ -64,7 +64,7 @@ <string name="info_row_connection">连接</string> <string name="connect_via_invitation_link">通过一次性链接进行连接?</string> <string name="connect_via_contact_link">通过联系人地址进行连接?</string> - <string name="connect_via_group_link">加群吗?</string> + <string name="connect_via_group_link">加入群组?</string> <string name="connect_via_link_or_qr">通过群组链接/二维码连接</string> <string name="always_use_relay">总是通过中继连接</string> <string name="allow_your_contacts_irreversibly_delete">允许您的联系人不不可逆地删除已发送消息。(24小时)</string> @@ -1630,4 +1630,89 @@ <string name="member_info_member_blocked">已封禁</string> <string name="snd_group_event_member_blocked">你封禁了 %s</string> <string name="snd_group_event_member_unblocked">你解封了 %s</string> + <string name="welcome_message_is_too_long">欢迎消息太大了</string> + <string name="database_migration_in_progress">正在进行数据库迁移。 +\n可能需要几分钟时间。</string> + <string name="message_too_large">消息太大了</string> + <string name="call_service_notification_video_call">视频通话</string> + <string name="call_service_notification_audio_call">语音通话</string> + <string name="call_service_notification_end_call">结束通话</string> + <string name="unable_to_open_browser_title">打开浏览器出错</string> + <string name="unable_to_open_browser_desc">没有默认网络浏览器无法使用通话功能。请在系统中配置默认浏览器并和开发者分享更多信息。</string> + <string name="e2ee_info_pq_short">此聊天受抗量子的端到端加密保护。</string> + <string name="e2ee_info_no_pq_short">此聊天受端到端加密保护。</string> + <string name="auth_open_migration_to_another_device">打开迁移屏幕</string> + <string name="migrate_from_another_device">从另一台设备迁移</string> + <string name="set_passphrase">设置密码短语</string> + <string name="conn_event_disabled_pq">标准端到端加密</string> + <string name="migrate_from_device_archive_and_upload">存档和上传</string> + <string name="migrate_from_device_verify_database_passphrase">验证数据库密码短语</string> + <string name="v5_6_quantum_resistant_encryption">抗量子加密</string> + <string name="v5_6_app_data_migration">应用数据迁移</string> + <string name="v5_6_app_data_migration_descr">通过二维码迁移到另一部设备。</string> + <string name="v5_6_picture_in_picture_calls">画中画通话</string> + <string name="v5_6_safer_groups">更安全的群组</string> + <string name="v5_6_picture_in_picture_calls_descr">通话时使用本应用</string> + <string name="migrate_to_device_title">迁移到此处</string> + <string name="or_paste_archive_link">或粘贴存档链接</string> + <string name="migrate_to_device_downloading_archive">正在下载存档</string> + <string name="invalid_file_link">无效链接</string> + <string name="migrate_to_device_migrating">迁移中</string> + <string name="paste_archive_link">粘贴存档链接</string> + <string name="migrate_to_device_database_init">正在准备下载</string> + <string name="migrate_to_device_download_failed">下载失败了</string> + <string name="migrate_to_device_repeat_download">重复下载</string> + <string name="migrate_to_device_bytes_downloaded">%s 已下载</string> + <string name="migrate_to_device_enter_passphrase">输入密码短语</string> + <string name="migrate_to_device_import_failed">导入失败了</string> + <string name="migrate_to_device_importing_archive">正在导入存档</string> + <string name="migrate_to_device_repeat_import">重复导入</string> + <string name="migrate_to_device_chat_migrated">已迁移聊天!</string> + <string name="migrate_to_device_error_downloading_archive">下载存档出错</string> + <string name="migrate_to_device_file_delete_or_link_invalid">文件被删除或链接无效</string> + <string name="migrate_to_device_finalize_migration">在另一部设备上完成迁移</string> + <string name="migrate_to_device_confirm_network_settings_footer">请确认网络设置对此这台设备正确无误。</string> + <string name="migrate_to_device_apply_onion">应用</string> + <string name="migrate_to_device_confirm_network_settings">确认网络设置</string> + <string name="migrate_from_device_error_exporting_archive">导出聊天数据库出错</string> + <string name="migrate_from_device_exported_file_doesnt_exist">导出的文件不存在</string> + <string name="migrate_from_device_to_another_device">迁移到另一部设备</string> + <string name="migrate_from_device_database_init">正在准备上传</string> + <string name="migrate_from_device_all_data_will_be_uploaded">你的所有联系人、对话和文件将被安全加密并分块上传到配置的 XFTP 中继。</string> + <string name="migrate_from_device_archiving_database">正在存档数据库</string> + <string name="migrate_from_device_confirm_upload">确认上传</string> + <string name="migrate_from_device_error_deleting_database">删除数据库出错</string> + <string name="migrate_from_device_chat_should_be_stopped">必须停止聊天才能继续。</string> + <string name="migrate_from_device_stopping_chat">正在停止聊天</string> + <string name="migrate_from_device_repeat_upload">重复上传</string> + <string name="migrate_from_device_bytes_uploaded">%s 已上传</string> + <string name="migrate_from_device_upload_failed">上传失败了</string> + <string name="migrate_from_device_uploading_archive">正在上传存档</string> + <string name="migrate_from_device_cancel_migration">取消迁移</string> + <string name="migrate_from_device_creating_archive_link">正在创建存档链接</string> + <string name="migrate_from_device_finalize_migration">完成迁移</string> + <string name="migrate_from_device_delete_database_from_device">从这部设备上删除数据库</string> + <string name="migrate_from_device_or_share_this_file_link">或安全地分享此文件链接</string> + <string name="migrate_from_device_starting_chat_on_multiple_devices_unsupported">警告:不支持在多部设备上启动聊天,这么做会导致消息传送失败。</string> + <string name="migrate_from_device_you_must_not_start_database_on_two_device"><![CDATA[你 <b>不能</b> 在两部设备上使用同一数据库。]]></string> + <string name="migrate_from_device_migration_complete">迁移完毕</string> + <string name="migrate_from_device_start_chat">启动聊天</string> + <string name="migrate_from_device_confirm_you_remember_passphrase">请在迁移前确认你记得数据库的密码短语。</string> + <string name="migrate_from_device_verify_passphrase">验证密码短语</string> + <string name="migrate_from_device_archive_will_be_deleted"><![CDATA[<b>警告</b>:该存档将被删除。]]></string> + <string name="migrate_from_device_check_connection_and_try_again">检查你的互联网连接并重试</string> + <string name="migrate_from_device_error_verifying_passphrase">验证密码短语出错:</string> + <string name="e2ee_info_no_pq"><![CDATA[消息、文件和通话受带完全前向保密、可否认性和试验性恢复的<b>端到端加密</b>保护。]]></string> + <string name="migrate_from_device_title">迁移设备</string> + <string name="migrate_from_device_choose_migrate_from_another_device"><![CDATA[在新设备上选择 <i>从一部设备迁移</i> 并扫描二维码。]]></string> + <string name="e2ee_info_pq"><![CDATA[消息、文件和通话受带完全前向保密、可否认性和试验性恢复的<b>抗量子端到端加密</b>保护。]]></string> + <string name="v5_6_safer_groups_descr">管理员可以为所有人封禁一名成员。</string> + <string name="migrate_to_device_downloading_details">正在下载链接详情</string> + <string name="v5_6_quantum_resistant_encryption_descr">在私聊中开启(公测)!</string> + <string name="migrate_from_device_error_saving_settings">保存设置出错</string> + <string name="migrate_from_device_error_uploading_archive">上传存档出错</string> + <string name="conn_event_enabled_pq">抗量子端到端加密</string> + <string name="migrate_to_device_try_again">你可以再试一次。</string> + <string name="migrate_from_device_try_again">你可以再试一次。</string> + <string name="migrate_from_device_using_on_two_device_breaks_encryption"><![CDATA[<b>请注意</b>: 作为安全保护措施,在两部设备上使用同一数据库会破坏解密来自你联系人的消息。]]></string> </resources> \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/android/call.html b/apps/multiplatform/common/src/commonMain/resources/assets/www/android/call.html index 7b51a05151..cbdf7a23a3 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/android/call.html +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/android/call.html @@ -8,6 +8,7 @@ <body> <video id="remote-video-stream" + class="inline" autoplay playsinline poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII=" @@ -15,6 +16,7 @@ ></video> <video id="local-video-stream" + class="inline" muted autoplay playsinline diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/android/style.css b/apps/multiplatform/common/src/commonMain/resources/assets/www/android/style.css index 3d2941c71e..571c420ad5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/android/style.css +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/android/style.css @@ -5,14 +5,14 @@ body { background-color: black; } -#remote-video-stream { +#remote-video-stream.inline { position: absolute; width: 100%; height: 100%; object-fit: cover; } -#local-video-stream { +#local-video-stream.inline { position: absolute; width: 30%; max-width: 30%; @@ -23,6 +23,20 @@ body { right: 0; } +#remote-video-stream.fullscreen { + position: absolute; + height: 100%; + width: 100%; + object-fit: cover; +} + +#local-video-stream.fullscreen { + position: absolute; + height: 100%; + width: 100%; + object-fit: cover; +} + *::-webkit-media-controls { display: none !important; -webkit-appearance: none !important; diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js b/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js index 6092eb39e5..5ea2f062b0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/call.js @@ -11,6 +11,12 @@ var VideoCamera; VideoCamera["User"] = "user"; VideoCamera["Environment"] = "environment"; })(VideoCamera || (VideoCamera = {})); +var LayoutType; +(function (LayoutType) { + LayoutType["Default"] = "default"; + LayoutType["LocalVideo"] = "localVideo"; + LayoutType["RemoteVideo"] = "remoteVideo"; +})(LayoutType || (LayoutType = {})); // for debugging // var sendMessageToNative = ({resp}: WVApiMessage) => console.log(JSON.stringify({command: resp})) var sendMessageToNative = (msg) => console.log(JSON.stringify(msg)); @@ -319,6 +325,10 @@ const processCommand = (function () { localizedDescription = command.description; resp = { type: "ok" }; break; + case "layout": + changeLayout(command.layout); + resp = { type: "ok" }; + break; case "end": endCall(); resp = { type: "ok" }; @@ -607,6 +617,28 @@ function toggleMedia(s, media) { } return res; } +function changeLayout(layout) { + const local = document.getElementById("local-video-stream"); + const remote = document.getElementById("remote-video-stream"); + switch (layout) { + case LayoutType.Default: + local.className = "inline"; + remote.className = "inline"; + local.style.visibility = "visible"; + remote.style.visibility = "visible"; + break; + case LayoutType.LocalVideo: + local.className = "fullscreen"; + local.style.visibility = "visible"; + remote.style.visibility = "hidden"; + break; + case LayoutType.RemoteVideo: + remote.className = "fullscreen"; + local.style.visibility = "hidden"; + remote.style.visibility = "visible"; + break; + } +} // Cryptography function - it is loaded both in the main window and in worker context (if the worker is used) function callCryptoFunction() { const initialPlainTextRequired = { diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/desktop/call.html b/apps/multiplatform/common/src/commonMain/resources/assets/www/desktop/call.html index dd1193babf..59ca2b58b0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/desktop/call.html +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/desktop/call.html @@ -9,6 +9,7 @@ <body> <video id="remote-video-stream" + class="inline" autoplay playsinline poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII=" @@ -16,6 +17,7 @@ ></video> <video id="local-video-stream" + class="inline" muted autoplay playsinline diff --git a/apps/multiplatform/common/src/commonMain/resources/assets/www/desktop/style.css b/apps/multiplatform/common/src/commonMain/resources/assets/www/desktop/style.css index 24c31fa6f7..80aff9e472 100644 --- a/apps/multiplatform/common/src/commonMain/resources/assets/www/desktop/style.css +++ b/apps/multiplatform/common/src/commonMain/resources/assets/www/desktop/style.css @@ -5,14 +5,14 @@ body { background-color: black; } -#remote-video-stream { +#remote-video-stream.inline { position: absolute; width: 100%; height: 100%; object-fit: cover; } -#local-video-stream { +#local-video-stream.inline { position: absolute; width: 20%; max-width: 20%; @@ -23,6 +23,20 @@ body { right: 0; } +#remote-video-stream.fullscreen { + position: absolute; + height: 100%; + width: 100%; + object-fit: cover; +} + +#local-video-stream.fullscreen { + position: absolute; + height: 100%; + width: 100%; + object-fit: cover; +} + *::-webkit-media-controls { display: none !important; -webkit-appearance: none !important; diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt index 0f7d140b0e..e65adea70e 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Images.desktop.kt @@ -17,14 +17,14 @@ import javax.imageio.stream.MemoryCacheImageOutputStream import kotlin.math.sqrt private fun errorBitmap(): ImageBitmap = - ImageIO.read(ByteArrayInputStream(Base64.getDecoder().decode("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAKVJREFUeF7t1kENACEUQ0FQhnVQ9lfGO+xggITQdvbMzArPey+8fa3tAfwAEdABZQspQStgBssEcgAIkSAJkiAJljtEgiRIgmUCSZAESZAESZAEyx0iQRIkwTKBJEiCv5fgvTd1wDmn7QAP4AeIgA4oW0gJWgEzWCZwbQ7gAA7ggLKFOIADOKBMIAeAEAmSIAmSYLlDJEiCJFgmkARJkARJ8N8S/ADTZUewBvnTOQAAAABJRU5ErkJggg=="))).toComposeImageBitmap() + ImageIO.read(ByteArrayInputStream(Base64.getMimeDecoder().decode("iVBORw0KGgoAAAANSUhEUgAAAEAAAABACAYAAACqaXHeAAAAAXNSR0IArs4c6QAAAKVJREFUeF7t1kENACEUQ0FQhnVQ9lfGO+xggITQdvbMzArPey+8fa3tAfwAEdABZQspQStgBssEcgAIkSAJkiAJljtEgiRIgmUCSZAESZAESZAEyx0iQRIkwTKBJEiCv5fgvTd1wDmn7QAP4AeIgA4oW0gJWgEzWCZwbQ7gAA7ggLKFOIADOKBMIAeAEAmSIAmSYLlDJEiCJFgmkARJkARJ8N8S/ADTZUewBvnTOQAAAABJRU5ErkJggg=="))).toComposeImageBitmap() actual fun base64ToBitmap(base64ImageString: String): ImageBitmap { val imageString = base64ImageString .removePrefix("data:image/png;base64,") .removePrefix("data:image/jpg;base64,") return try { - ImageIO.read(ByteArrayInputStream(Base64.getDecoder().decode(imageString))).toComposeImageBitmap() + ImageIO.read(ByteArrayInputStream(Base64.getMimeDecoder().decode(imageString))).toComposeImageBitmap() } catch (e: IOException) { Log.e(TAG, "base64ToBitmap error: $e") errorBitmap() @@ -77,7 +77,7 @@ actual fun compressImageStr(bitmap: ImageBitmap): String { return try { val encoded = Base64.getEncoder().encodeToString(compressImageData(bitmap, usePng).toByteArray()) "data:image/$ext;base64,$encoded" - } catch (e: IOException) { + } catch (e: Exception) { Log.e(TAG, "resizeImageToStrSize error: $e") throw e } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt new file mode 100644 index 0000000000..1caf96902d --- /dev/null +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/ScrollableColumn.desktop.kt @@ -0,0 +1,129 @@ +package chat.simplex.common.platform + +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D +import androidx.compose.foundation.* +import androidx.compose.foundation.gestures.FlingBehavior +import androidx.compose.foundation.interaction.* +import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.* +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.MaterialTheme +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.graphicsLayer +import androidx.compose.ui.input.pointer.* +import androidx.compose.ui.unit.dp +import chat.simplex.common.views.helpers.detectCursorMove +import chat.simplex.common.views.helpers.mixWith +import kotlinx.coroutines.* + +@Composable +actual fun LazyColumnWithScrollBar( + modifier: Modifier, + state: LazyListState, + contentPadding: PaddingValues, + reverseLayout: Boolean, + verticalArrangement: Arrangement.Vertical, + horizontalAlignment: Alignment.Horizontal, + flingBehavior: FlingBehavior, + userScrollEnabled: Boolean, + content: LazyListScope.() -> Unit +) { + if (appPlatform.isAndroid) { + LazyColumn(modifier, state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) + } else { + val scope = rememberCoroutineScope() + val scrollBarAlpha = remember { Animatable(0f) } + val scrollJob: MutableState<Job> = remember { mutableStateOf(Job()) } + val scrollModifier = remember { + Modifier + .pointerInput(Unit) { + detectCursorMove { + scope.launch { + scrollBarAlpha.animateTo(1f) + } + scrollJob.value.cancel() + scrollJob.value = scope.launch { + delay(1000L) + scrollBarAlpha.animateTo(0f) + } + } + } + } + LazyColumn(modifier.then(if (appPlatform.isDesktop) scrollModifier else Modifier), state, contentPadding, reverseLayout, verticalArrangement, horizontalAlignment, flingBehavior, userScrollEnabled, content) + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd) { + DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, reverseLayout) + } + } +} + +@Composable +actual fun ColumnWithScrollBar( + modifier: Modifier, + verticalArrangement: Arrangement.Vertical, + horizontalAlignment: Alignment.Horizontal, + state: ScrollState, + content: @Composable ColumnScope.() -> Unit +) { + val scope = rememberCoroutineScope() + val scrollBarAlpha = remember { Animatable(0f) } + val scrollJob: MutableState<Job> = remember { mutableStateOf(Job()) } + val scrollModifier = remember { + Modifier + .pointerInput(Unit) { + detectCursorMove { + scope.launch { + scrollBarAlpha.animateTo(1f) + } + scrollJob.value.cancel() + scrollJob.value = scope.launch { + delay(1000L) + scrollBarAlpha.animateTo(0f) + } + } + } + } + Column(modifier.verticalScroll(state).then(scrollModifier), verticalArrangement, horizontalAlignment, content) + Box(Modifier.fillMaxSize(), contentAlignment = Alignment.CenterEnd) { + DesktopScrollBar(rememberScrollbarAdapter(state), Modifier.align(Alignment.CenterEnd).fillMaxHeight(), scrollBarAlpha, scrollJob, false) + } +} + +@Composable +fun DesktopScrollBar(adapter: androidx.compose.foundation.v2.ScrollbarAdapter, modifier: Modifier, scrollBarAlpha: Animatable<Float, AnimationVector1D>, scrollJob: MutableState<Job>, reversed: Boolean) { + val scope = rememberCoroutineScope() + val interactionSource = remember { MutableInteractionSource() } + val isHovered by interactionSource.collectIsHoveredAsState() + val isDragged by interactionSource.collectIsDraggedAsState() + LaunchedEffect(isHovered, isDragged) { + scrollJob.value.cancel() + if (isHovered || isDragged) { + scrollBarAlpha.animateTo(1f) + } else { + scrollJob.value = scope.launch { + delay(1000L) + scrollBarAlpha.animateTo(0f) + } + } + } + VerticalScrollbar( + modifier = modifier.graphicsLayer { alpha = scrollBarAlpha.value } + .onPointerEvent(PointerEventType.Enter) { + scrollJob.value.cancel() + scope.launch { + scrollBarAlpha.animateTo(1f) + } + }, + reverseLayout = reversed, + style = LocalScrollbarStyle.current.copy( + thickness = if (isHovered || isDragged) 10.dp else 6.dp, + shape = RoundedCornerShape(if (isHovered || isDragged) 5.dp else 3.dp), + unhoverColor = if (MaterialTheme.colors.isLight) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.7f) else MaterialTheme.colors.onBackground.mixWith(MaterialTheme.colors.background, 0.3f), + hoverColor = if (MaterialTheme.colors.isLight) MaterialTheme.colors.background.mixWith(MaterialTheme.colors.onBackground, 0.6f) else MaterialTheme.colors.onBackground.mixWith(MaterialTheme.colors.background, 0.4f) + ), + adapter = adapter, + interactionSource = interactionSource + ) +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt index 22d39409ed..87c2d3e8f2 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/call/CallView.desktop.kt @@ -75,7 +75,7 @@ actual fun ActiveCallView() { chatModel.activeCall.value = call.copy(callState = CallState.Connected, connectedAt = Clock.System.now()) } withBGApi { chatModel.controller.apiCallStatus(callRh, call.contact, callStatus) } - } catch (e: Error) { + } catch (e: Throwable) { Log.d(TAG, "call status ${r.state.connectionState} not used") } is WCallResponse.Connected -> { @@ -146,8 +146,21 @@ private fun SendStateUpdates() { @Composable fun WebRTCController(callCommand: SnapshotStateList<WCallCommand>, onResponse: (WVAPIMessage) -> Unit) { val uriHandler = LocalUriHandler.current + val endCall = { + val call = chatModel.activeCall.value + if (call != null) withBGApi { chatModel.callManager.endCall(call) } + } val server = remember { - uriHandler.openUri("http://${SERVER_HOST}:$SERVER_PORT/simplex/call/") + try { + uriHandler.openUri("http://${SERVER_HOST}:$SERVER_PORT/simplex/call/") + } catch (e: Exception) { + Log.e(TAG, "Unable to open browser: ${e.stackTraceToString()}") + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.unable_to_open_browser_title), + text = generalGetString(MR.strings.unable_to_open_browser_desc) + ) + endCall() + } startServer(onResponse) } fun processCommand(cmd: WCallCommand) { diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt index 13d2f8b1e4..c2333393e5 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.desktop.kt @@ -3,7 +3,6 @@ package chat.simplex.common.views.chatlist import androidx.compose.foundation.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.CircleShape -import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.runtime.* @@ -13,6 +12,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.unit.dp import chat.simplex.common.platform.* import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.call.Call import chat.simplex.common.views.call.CallMediaType import chat.simplex.common.views.chat.item.ItemAction import chat.simplex.common.views.helpers.* @@ -22,10 +22,9 @@ import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.flow.MutableStateFlow @Composable -actual fun DesktopActiveCallOverlayLayout(newChatSheetState: MutableStateFlow<AnimatedViewState>) { - val call = remember { chatModel.activeCall}.value - // if (call?.callState == CallState.Connected && !newChatSheetState.collectAsState().value.isVisible()) { - if (call != null && !newChatSheetState.collectAsState().value.isVisible()) { +actual fun ActiveCallInteractiveArea(call: Call, newChatSheetState: MutableStateFlow<AnimatedViewState>) { + // if (call.callState == CallState.Connected && !newChatSheetState.collectAsState().value.isVisible()) { + if (!newChatSheetState.collectAsState().value.isVisible()) { val showMenu = remember { mutableStateOf(false) } val media = call.peerMedia ?: call.localMedia CompositionLocalProvider( diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.desktop.kt index af2b269b58..eb93e7c510 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/database/DatabaseEncryptionView.desktop.kt @@ -2,6 +2,7 @@ package chat.simplex.common.views.database import SectionItemView import SectionTextFooter +import TextIconSpaced import androidx.compose.foundation.layout.* import androidx.compose.material.* import androidx.compose.runtime.Composable @@ -22,8 +23,9 @@ actual fun SavePassphraseSetting( useKeychain: Boolean, initialRandomDBPassphrase: Boolean, storedKey: Boolean, - progressIndicator: Boolean, minHeight: Dp, + enabled: Boolean, + smallPadding: Boolean, onCheckedChange: (Boolean) -> Unit, ) { SectionItemView(minHeight = minHeight) { @@ -33,7 +35,11 @@ actual fun SavePassphraseSetting( stringResource(MR.strings.save_passphrase_in_settings), tint = if (storedKey) WarningOrange else MaterialTheme.colors.secondary ) - Spacer(Modifier.padding(horizontal = 4.dp)) + if (smallPadding) { + Spacer(Modifier.padding(horizontal = 4.dp)) + } else { + TextIconSpaced(false) + } Text( stringResource(MR.strings.save_passphrase_in_settings), Modifier.padding(end = 24.dp), @@ -43,7 +49,7 @@ actual fun SavePassphraseSetting( DefaultSwitch( checked = useKeychain, onCheckedChange = onCheckedChange, - enabled = !initialRandomDBPassphrase && !progressIndicator + enabled = enabled ) } } @@ -55,13 +61,14 @@ actual fun DatabaseEncryptionFooter( chatDbEncrypted: Boolean?, storedKey: MutableState<Boolean>, initialRandomDBPassphrase: MutableState<Boolean>, + migration: Boolean, ) { if (chatDbEncrypted == false) { SectionTextFooter(generalGetString(MR.strings.database_is_not_encrypted)) } else if (useKeychain.value) { if (storedKey.value) { SectionTextFooter(generalGetString(MR.strings.settings_is_storing_in_clear_text)) - if (initialRandomDBPassphrase.value) { + if (initialRandomDBPassphrase.value && !migration) { SectionTextFooter(generalGetString(MR.strings.encrypted_with_random_passphrase)) } else { SectionTextFooter(annotatedStringResource(MR.strings.impossible_to_recover_passphrase)) diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt index 1a8ad010e3..5ffc68be79 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/views/usersettings/Appearance.desktop.kt @@ -4,8 +4,6 @@ import SectionBottomSpacer import SectionDividerSpaced import SectionView import androidx.compose.foundation.layout.* -import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.verticalScroll import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable @@ -13,6 +11,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import chat.simplex.common.model.ChatModel import chat.simplex.common.model.SharedPreference +import chat.simplex.common.platform.ColumnWithScrollBar import chat.simplex.common.platform.defaultLocale import chat.simplex.common.ui.theme.ThemeColor import chat.simplex.common.views.helpers.* @@ -43,8 +42,8 @@ fun AppearanceScope.AppearanceLayout( showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), editColor: (ThemeColor, Color) -> Unit, ) { - Column( - Modifier.fillMaxWidth().verticalScroll(rememberScrollState()), + ColumnWithScrollBar( + Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.appearance_settings)) SectionView(stringResource(MR.strings.settings_section_title_language), padding = PaddingValues()) { diff --git a/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt b/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt index e32b0ae79a..7171f17991 100644 --- a/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt +++ b/apps/multiplatform/desktop/src/jvmMain/kotlin/chat/simplex/desktop/Main.kt @@ -1,7 +1,19 @@ package chat.simplex.desktop +import androidx.compose.animation.core.Animatable +import androidx.compose.animation.core.AnimationVector1D +import androidx.compose.foundation.* +import androidx.compose.foundation.interaction.* +import androidx.compose.foundation.lazy.LazyListState +import androidx.compose.runtime.* +import androidx.compose.ui.ExperimentalComposeUiApi +import androidx.compose.ui.Modifier +import androidx.compose.ui.input.pointer.* import chat.simplex.common.platform.* +import chat.simplex.common.platform.DesktopPlatform import chat.simplex.common.showApp +import chat.simplex.common.views.helpers.* +import kotlinx.coroutines.* import java.io.File fun main() { @@ -12,6 +24,7 @@ fun main() { return showApp() } +@OptIn(ExperimentalComposeUiApi::class) @Suppress("UnsafeDynamicallyLoadedCode") private fun initHaskell() { val resourcesDir = File(System.getProperty("compose.application.resources.dir")) @@ -27,6 +40,40 @@ private fun initHaskell() { System.setProperty("jna.library.path", vlcDir.absolutePath) //discoverVlcLibs(File(File(vlcDir, "vlc"), "plugins").absolutePath) initHS() + + platform = object: PlatformInterface { + @Composable + override fun desktopScrollBarComponents(): Triple<Animatable<Float, AnimationVector1D>, Modifier, MutableState<Job>> { + val scope = rememberCoroutineScope() + val scrollBarAlpha = remember { Animatable(0f) } + val scrollJob: MutableState<Job> = remember { mutableStateOf(Job()) } + val modifier = remember { + Modifier.pointerInput(Unit) { + detectCursorMove { + scope.launch { + scrollBarAlpha.animateTo(1f) + } + scrollJob.value.cancel() + scrollJob.value = scope.launch { + delay(1000L) + scrollBarAlpha.animateTo(0f) + } + } + } + } + return Triple(scrollBarAlpha, modifier, scrollJob) + } + + @Composable + override fun desktopScrollBar(state: LazyListState, modifier: Modifier, scrollBarAlpha: Animatable<Float, AnimationVector1D>, scrollJob: MutableState<Job>, reversed: Boolean) { + DesktopScrollBar(rememberScrollbarAdapter(scrollState = state), modifier, scrollBarAlpha, scrollJob, reversed) + } + + @Composable + override fun desktopScrollBar(state: ScrollState, modifier: Modifier, scrollBarAlpha: Animatable<Float, AnimationVector1D>, scrollJob: MutableState<Job>, reversed: Boolean) { + DesktopScrollBar(rememberScrollbarAdapter(scrollState = state), modifier, scrollBarAlpha, scrollJob, reversed) + } + } } private fun windowsLoadRequiredLibs(libsTmpDir: File, vlcDir: File) { diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 5a556d5f82..a3e9ef0990 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -25,11 +25,11 @@ android.nonTransitiveRClass=true android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 -android.version_name=5.5.6 -android.version_code=187 +android.version_name=5.6 +android.version_code=191 -desktop.version_name=5.5.6 -desktop.version_code=32 +desktop.version_name=5.6 +desktop.version_code=35 kotlin.version=1.8.20 gradle.plugin.version=7.4.2 diff --git a/apps/simplex-broadcast-bot/src/Broadcast/Options.hs b/apps/simplex-broadcast-bot/src/Broadcast/Options.hs index bce0f94972..57986874aa 100644 --- a/apps/simplex-broadcast-bot/src/Broadcast/Options.hs +++ b/apps/simplex-broadcast-bot/src/Broadcast/Options.hs @@ -80,6 +80,7 @@ mkChatOpts BroadcastBotOpts {coreOptions} = chatCmdLog = CCLNone, chatServerPort = Nothing, optFilesFolder = Nothing, + optTempDirectory = Nothing, showReactions = False, allowInstantFiles = True, autoAcceptFileSize = 0, diff --git a/apps/simplex-directory-service/src/Directory/Options.hs b/apps/simplex-directory-service/src/Directory/Options.hs index 78157d7e11..0d64064d7d 100644 --- a/apps/simplex-directory-service/src/Directory/Options.hs +++ b/apps/simplex-directory-service/src/Directory/Options.hs @@ -80,6 +80,7 @@ mkChatOpts DirectoryOpts {coreOptions} = chatCmdLog = CCLNone, chatServerPort = Nothing, optFilesFolder = Nothing, + optTempDirectory = Nothing, showReactions = False, allowInstantFiles = True, autoAcceptFileSize = 0, diff --git a/blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md b/blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md new file mode 100644 index 0000000000..ea4249f250 --- /dev/null +++ b/blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md @@ -0,0 +1,260 @@ +--- +layout: layouts/article.html +title: "SimpleX Chat v5.6 (beta): adding quantum resistance to Signal double ratchet algorithm" +date: 2024-03-14 +previewBody: blog_previews/20240314.html +image: images/20240314-kem.jpg +imageWide: true +permalink: "/blog/20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.html" +--- + +# SimpleX Chat v5.6 beta: adding quantum resistance to Signal double ratchet algorithm + +This is a major upgrade for SimpleX messaging protocols, we are really proud to present the results of the hard work of our whole team on the [Pi day](https://en.wikipedia.org/wiki/Pi_Day). + +This post also covers various aspects of end-to-end encryption, compares different messengers, and explains why and how quantum-resistant encryption is added to SimpleX Chat: + +- [Why do we need end-to-end encryption?](#why-do-we-need-end-to-end-encryption) +- [Why encryption is even allowed?](#why-encryption-is-even-allowed) +- [End-to-end encryption security: attacks and defense.](#end-to-end-encryption-security-attacks-and-defense) + - Compromised message size - mitigated by padding messages to a fixed block size. + - Compromised confidentiality - mitigated by repudiation (deniability). + - Compromised message keys - mitigated by forward secrecy. + - Compromised long-term or session - mitigated by break-in recovery. + - Man-in-the-middle attack - mitigated by two-factor key exchange. + - "Record now, decrypt later" attacks - mitigated by post-quantum cryptography. +- [How secure is encryption in different messengers?](#how-secure-is-end-to-end-encryption-in-different-messengers) +- [Adding quantum resistance to Signal double ratchet algorithm.](#adding-quantum-resistance-to-signal-double-ratchet-algorithm) +- [When can you start using quantum resistant chats?](#when-can-you-start-using-quantum-resistant-chats) +- [Next for post-quantum crypto - all direct chats, small groups and security audit.](#next-for-post-quantum-crypto---all-direct-chats-small-groups-and-security-audit) + +## Why do we need end-to-end encryption? + +The objective of end-to-end encryption is to make any potential attackers, such as traffic observers or communication providers who pass the messages between senders and recipients, unable to recover *any* message content or meaningful information about the messages, even if these attackers possess very advanced computing and mathematical capabilities. + +While human eyes are unable to see any difference between simply scrambled and encrypted messages, the difference between unreadable scrambling and unbreakable encryption can be as huge as just a few seconds to unscramble a message on an average laptop and more time than the Universe existed required to break the encryption on the most powerful computer in the world. + +Achieving the latter requires a lot of mathematical precision in both the cryptographic algorithms and in how they are used, and effectively makes encrypted messages indistinguishable from random noise, without any discoverable patterns or statistical irregularities that a computer could use to break the message encryption any faster than it it would take to try every possible combination of bits in the key. + +End-to-end encryption is an important component of our individual and business security, privacy and sovereignty. Having our private communications protected from any observers is both the natural condition and our inalienable human right. + +It's very sad to see the same people who keep their financial affairs private to protect from financial crimes, lock their doors to protect from thieves, and curtain their windows to protect from the occasional prying eyes, when it comes to protecting their personal lives from the data criminals say "we don't care about privacy, we have nothing to hide". Everybody's safety depends on keeping their affairs and relations private, not visible to a vast and ruthless data gathering machines, that abuse our data for commercial gain, without any regard to our interests or even [the safety of our families and children](https://nmdoj.gov/press-release/attorney-general-raul-torrez-files-lawsuit-against-meta-platforms-and-mark-zuckerberg-to-protect-children-from-sexual-abuse-and-human-trafficking/). + +## Why encryption is even allowed? + +<img src="./images/20240314-djb.jpg" class="float-to-right"> + +If encryption is such a powerful tool to protect our lives, it also can be used to conceal crimes, so why the governments don't consider it similar to arms, and don't heavily regulate its use? + +Prior to 1996 the cryptography was considered munition, and its export from the United States was controlled under this category, [alongside flamethrowers and B-1 bombers](https://cr.yp.to/export/1995/0303-eff.txt). When [Daniel J. Bernstein](https://en.wikipedia.org/wiki/Daniel_J._Bernstein) (DJB), then a student of Mathematics at University of California, Berkeley, wanted to publish the paper and the source code of his Snuffle encryption system, the Office of Defense Trade Controls of the Department of State (DOS) after more than a year of correspondence requested that DJB registers as the arms dealer. + +In 1995 DJB represented by the Electronic Frontier Foundation brought a case against the DOS to overturn cryptography restrictions. The ruling in the case declared that the export control over cryptographic software and related technical data constitute [an impermissible infringement on speech in violation of the First Amendment](https://cr.yp.to/export/1996/1206-order.txt). This decision resulted in regulatory changes, reducing controls on encryption exports, particularly for open-source algorithms. The case continued until 2003, when it was put on hold after the commitment from the US government not to enforce any remaining regulations. + +This case is very important for the whole industry, as to this day we can freely create and use open-source cryptography without export control restrictions. It also shows the importance of engaging with the system and challenging its views in an open dialogue, rather than either blindly complying or violating regulations. + +DJB role for cryptography and open-source goes beyond this case – many cryptographic algorithms that are considered to be the most advanced, and many of which we use in SimpleX Chat, were designed and developed by him: + +- Ed25519 cryptographic signature algorithm we use to authorize commands to the servers. +- NaCL library with cryptobox and secretbox constructions that combine X25519 Diffie-Hellman key agreement with Salsa20 encryption and Poly1305 authentication. We use cryptobox to encrypt messages in two of three encryption layers and secretbox to encrypt files. +- Streamlined NTRU Prime algorithm for quantum resistant key agreement that we used in the protocol for linking mobile app with desktop, and now added to Signal double ratchet algorithm, as explained below. + +Without DJB's work the world would have been in a much worse place privacy- and security-wise. + +Daniel, we are really grateful for the work you did and continue doing. Thank you, and congratulations on the International Mathematics Day! + +## End-to-end encryption security: attacks and defense + +End-to-end encryption is offered by many messaging apps and protocols, but the security of different implementations are not the same. While many users know about the importance of forward secrecy - the quality of end-to-end encryption that preserves security of the encryption of the past messages, even if the keys used to encrypt some of the messages were compromised - there are many other qualities that protect from different attacks. Below there is the overview of these attacks and the properties of end-to-end encryption schemes that mitigate these attacks. + +### 1. Compromised message size - mitigated by padding messages to a fixed block size + +While the content encryption is the most important, concealing the actual message size is almost as important for several reasons: + +- attacker able to observe even approximate message sizes can use these sizes as an additional signal for machine learning to de-anonymise the users and to categorize the relationships between the users. +- if a messenger conceals the routing of the messages to hide the transport identities (IP addresses) of senders and recipients, message sizes can be used by traffic observers to confirm the fact of communication with a much higher degree of certainty. + +The only effective mitigation to these attacks is to pad all messages to a fixed size. Using space-efficient schemes like Padme, or padding to encryption block size is ineffective for mitigating these attacks, as they still allow differentiating message sizes. + +To the best of our knowledge the only messenger other than SimpleX Chat that padded all messages to a fixed packet size was [Pond](https://github.com/agl/pond) - SimpleX design as an evolution of it. + +### 2. Compromised confidential messages - mitigated by repudiation (deniability) + +Many users are very interested in having ability to irreversibly delete sent messages from the recipients devices. But not only would this ability violate data sovereignty of device owners, it is also completely ineffective, as the recipients could simply put the device offline or use a modified client app to ignore message deletion requests. While SimpleX Chat provides such features as [disappearing messages](./20230103-simplex-chat-v4.4-disappearing-messages.md#disappearing-messages) and the ability to [irreversibly delete sent messages](./20221206-simplex-chat-v4.3-voice-messages.md#irreversible-message-deletion) provided both parties agree to that, these are convenience features, and they cannot be considered security measures. + +The solution to that is well known to cryptographers - it is the quality of the encryption algorithms called "repudiation", sometimes also called "deniability". This is the ability of the senders to plausibly deny having sent any messages, because cryptographic algorithms used to encrypt allow recipients forging these messages on their devices, so while the encryption proves authenticity of the message to the recipient, it cannot be used as a proof to any third party. + +Putting it all in a simpler language - a sender can claim that the recipient forged messages on their device, and deny ever having sent them. The recipient will not be able to provide any cryptographic proof. This quality makes digital conversation having the same qualities as private off-the-record conversation - that's why the family of algorithms that provide these qualities are called off-the-record (OTR) encryption. + +Repudiation is still a rather new concept - the first off-the-record algorithms were proposed in 2004 and were only offered to a wide range of users in Signal messenger. This concept is still quite badly understood by users and society, and yet to have been used as the defense in any public court cases, as legal systems evolve much slower than technology. In high profile cases repudiation can be used as an effective evidence for the defense. + +Repudiation in messaging systems can be undermined by adding cryptographic signature to the protocol, and many messengers that use OTR encryption algorithms do exactly that, unfortunately. SimpleX Chat does not use signature in any part of client-client protocol, but the signature is currently used when authorizing sender's messages to the relays. v5.7 will improve deniability by enabling a different authorization scheme that will provide full-stack repudiation in all protocol layers. + +### 3. Compromised message keys - mitigated by forward secrecy + +The attacker who obtained or broke the keys used to encrypt individual messages, may try to use these keys to decrypt past or future messages. This attack is unlikely to succeed via message interception, and it is likely to require breaking into the device storage. But in any case, if the key was broken or obtained in some other way it's important that this key cannot be used to decrypt other messages - this is achieved by forward secrecy. + +This property is well understood by the users, and most messengers that focus on privacy and security, with the exception of Session, provide forward secrecy as part of their encryption schemes design. + +### 4. Compromised long-term or session - mitigated by break-in recovery + +This attack is much less understood by the users, and forward secrecy does not protect from it. Arguably, it's almost impossible to compromise individual message keys without compromising long-term or session keys. So the ability of the encryption to recover from break-in (attacker making a copy of the device data without retaining the ongoing access) is both very and pragmatic - break-in attacks are simpler to execute on mobile devices during short-term device access than long-term ongoing compromise. + +Out of all encryption algorithms known to us only Signal double ratchet algorithm provides the ability to encryption security after break-ins. This recovery happens automatically and transparently to the users, without them doing anything special even knowing about break-in, by simply sending messages. Every time one of the communication parties replies to another party message, new random keys are generated and previously stolen keys become useless. + +Signal double ratchet algorithm is used in Signal, Cwtch and SimpleX Chat. This is why you cannot use SimpleX Chat profile on more than one device at the same time - the encryption scheme rotates the long term keys, randomly, and keys on another device become useless, as they would become useless for the attacker who stole them. Security always has some costs to the convenience. + +### 5. Man-in-the-middle attack - mitigated by two-factor key exchange + +Many people incorrectly believe that security of end-to-end encryption cannot be broken by communication provider. But end-to-end encryption is as secure as key exchange. While any intermediary passing the keys between senders and recipients cannot recover the private keys from the public keys, they can simply replace the passed public keys with their own and then proxy all communication between the users having full access to the original messages. So instead of having an end-to-end encrypted channel, users would have two half-way encrypted channels - between users and their communication intermediary. + +Pictures below illustrate how this attack works for RSA encryption. + +#### 1) Alice sends the key to Bob (e.g. via p2p network or via the messaging server). + +![Public key is shared](./images/20240314-mitm1.jpg) + +#### 2) Now Bob can send encrypted messages to Alice - he believes they are secure! + +![Message is encrypted](./images/20240314-mitm2.jpg) + +#### 3) But the key could have been intercepted and substituted by Tom (the attacker, or a service provider). + +![Key is intercepted and replaced](./images/20240314-mitm3.jpg) + +#### 4) Now the attacker can read the messages without Alice and Bob knowing. + +![End-to-end encryption is compromised](./images/20240314-mitm4.jpg) + +The attack on Diffie-Hellman (or on quantum-resistant) key exchange, when both parties send their public keys (or public key and ciphertext), requires the attacker to intercept and replace both keys, but the outcome remains the same - if all communication is passed via a single channel, as it is usually the case with communication services, then any attacker that has inside access to the service can selectively compromise some of the conversations. Two years ago I wrote the post about this [vulnerability of end-to-end encryption to MITM attacks](https://www.poberezkin.com/posts/2022-12-07-why-privacy-needs-to-be-redefined.html#e2e-encryption-is-not-bulletproof). + +All known mitigations of this attack require using the secondary communication channel to ensure that the keys have not been substituted. The most secure approach is to make user's key (or key fingerprint) a part of the user's address or connection link, thus making two-factor key exchange non-optional. This approach is used in Session, Cwtch and SimpleX Chat. + +A less secure approach is to provide users an optional way to compare security codes - this is what is done by Signal, Element and many other messengers. The problem with this post-key-exchange verification is that it is optional, and is usually skipped by the majority of the users. Also, this security code can change because the user changed the device, or as a result of the attack via the service provider. When you see in the client app the notification that the security code changed, it's pointless to ask in the same messenger whether the device was changed, as if it were an attack, the attacker would simply confirm it. Instead, the security code needs to be re-validated again via another channel. A good security practice for the users would be to warn their communication partners about the intention to switch the device in advance, before the security code is changed. + +### 6. "Record now, decrypt later" attacks - mitigated by post-quantum cryptography. + +This is the idea based on the assumption that commercially viable quantum computers will become available during the next 10 years, and then they can use time-efficient [Shor's algorithm](https://en.wikipedia.org/wiki/Shor%27s_algorithm) developed in 1994 to break asymmetric encryption with quantum computer (symmetric encryption is not vulnerable to this algorithm). + +Post-quantum cryptography, or encryption algorithms that are resistant to quantum computers, has been the area of ongoing research for several decades, and there are some algorithms that _might_ protect from quantum computers. It's important to account for these limitations: + +- _none of the post-quantum algorithms are proven to be secure_ against quantum or conventional computers. They are usually referred to as "believed to be secure" by the researchers and security experts. There is continuous research to break post-quantum algorithms, and to prove their security, and many of these algorithms are broken every year, often by conventional computers. +- because of the lack of proofs or guarantees that post-quantum cryptography delivers on its promise, these algorithms can only be used in hybrid encryption schemes to augment conventional cryptography, and never to replace it, contrary to some expert recommendations, as DJB explains in this [blog post](https://blog.cr.yp.to/20240102-hybrid.html). +- they are much more computationally expensive and less space efficient, and the encryption schemes have to balance their usability and security. +- many of post-quantum algorithms have known patent claims, so any system deploying them accepts the risks of patent litigation. +- the silver lining to these limitations is that the risk of appearance of commercially viable quantum computers in the next decade may be exaggerated. + +So, to put it bluntly and provocatively, post-quantum cryptography can be compared with a remedy against the illness that nobody has, without any guarantee that it will work. The closest analogy in the history of medicine is _snake oil_. + +<img src="./images/20240314-datacenter.jpg" width="400" class="float-to-right"> + +Does it mean that post-quantum cryptography is useless and should be ignored? Absolutely not. The risks of "record now, decrypt later" attacks are real, particularly for high profile targets, including millions of people - journalists, whistle-blowers, freedom-fighters in oppressive regimes, and even some ordinary people who may become targets of information crimes. Large scale collection of encrypted communication data is ongoing, and this data may be used in the future. So having the solution that _may_ protect you (post-quantum cryptography), as long as it doesn't replace the solution that is _proven_ to protect you (conventional cryptography), is highly beneficial in any communication solution, and has already been deployed in many tools and in some messengers. + +## How secure is end-to-end encryption in different messengers? + +This comparison may be incorrect in some of the columns. We apologize if some of the points are incorrect, please let us know about any mistakes so we can amend them! + +The main objective here is to establish the framework for comparing the security of end-to-end encryption schemes, and to highlight any areas for improvement, not to criticize any implementations. + +![Messengers comparison](./images/20240314-comparison.jpg) + +<sup>1</sup> Repudiation in SimpleX Chat will include client-server protocol from v5.7 or v5.8. Currently it is implemented but not enabled yet, as its support requires releasing the relay protocol that breaks backward compatibility. + +<sup>2</sup> Post-quantum cryptography is available in beta version, as opt-in only for direct conversations. See below how it will be rolled-out further. + +Some columns are marked with a yellow checkmark: +- when messages are padded, but not to a fixed size. +- when repudiation does not include client-server connection. In case of Cwtch it appears that the presence of cryptographic signatures compromises repudiation (deniability), but it needs to be clarified. +- when 2-factor key exchange is optional, via security code verification. +- when post-quantum cryptography is only added to the initial key agreement, does not protect break-in recovery. + +## Adding quantum resistance to Signal double ratchet algorithm + +We have been exploring post-quantum cryptography since early 2022, when SimpleX Chat was first released, and we did not want to be pioneers here - cryptography is critically important to make it right. + +We hoped to adopt the algorithm that will be standardized by NIST, but the standardization process turned out to be hugely disappointing, and the ML-KEM (Kyber) algorithm that was accepted as a standard was modified to remove an important hashing step (see the lines 304-314 in [the published spec](https://nvlpubs.nist.gov/nistpubs/FIPS/NIST.FIPS.203.ipd.pdf))), that mitigates the attacks via a compromised random numbers generator, ignoring strong criticism from many expert cryptographers, including DJB (see [this discussion](https://groups.google.com/a/list.nist.gov/g/pqc-forum/c/WFRDl8DqYQ4) and [the comments NIST received](https://csrc.nist.gov/files/pubs/fips/203/ipd/docs/fips-203-initial-public-comments-2023.pdf)). To make it even worse, the calculation of security levels of Kyber appears to have been done incorrectly, and overall, the chosen Kyber seems worse than rejected NTRU according to [the analysis by DJB](https://blog.cr.yp.to/20231003-countcorrectly.html). + +We also analyzed the encryption schemes proposed in Tutanota in 2021, and another scheme adopted by Signal last year, and published the design of [quantum resistant double ratchet algorithm](https://github.com/simplex-chat/simplex-chat/blob/stable/docs/rfcs/2023-09-30-pq-double-ratchet.md) that we believe provides better security than these schemes: + +- unlike Tutanota design, it augments rather than replaces conventional cryptography, and also avoids using signatures when the new keys are agreed (ratchet steps). +- unlike other messengers that adopted or plan to adopt ML-KEM, we used Streamlined NTRU Prime algorithm (specifically, strnup761) that has no problems of ML-KEM, no known patent claims, and seems less likely to be compromised than other algorithms - it is exactly the same algorithm that is used in SSH. You can review the comparison of [the risks of various post-quantum algorithms](https://ntruprime.cr.yp.to/warnings.html). +- unlike Signal design that only added quantum resistance to the initial key exchange by replacing X3DH key agreement scheme with post-quantum [PQXDH](https://signal.org/docs/specifications/pqxdh/), but did not improve Signal algorithm itself, our design added quantum-resistant key agreements inside double algorithm, making its break-in recovery property also quantum resistant. + +The we could make break-in recovery property of Signal algorithm quantum-resistant, and why, probably, Signal didn't, is because irrespective of the message size SimpleX Chat uses a fixed block size of 16kb to provide security and privacy against any traffic observers and against messaging relays. So we had an extra space to accommodate additional ~2.2kb worth of keys in each message without any additional traffic costs. + +In case when the message is larger than the remaining block size, e.g. when the message contains image or link preview, or a large text, we used [zstd compression](https://en.wikipedia.org/wiki/Zstd) to provide additional space for the required keys without reducing image preview quality or creating additional traffic - our previously inefficient JSON encoding of chat messages was helpful in this case. + +<image src="./images/20240314-kem.jpg" alt="Double KEM agreement" width="500" class="float-to-right"> + +The additional challenge in adding sntrup761 was that unlike Diffie-Hellman key exchange, which is symmetric (that is, the parties can share their public keys in any order and the shared secret can be computed from two public keys), sntrup761 is interactive key-encapsulation mechanism (KEM) that requires that one party shares its public key, and another party uses it to encapsulate (which is a fancy term for "encrypt" - that is why it has asterisks in the image) a random shared secret, and sends it back - making it somewhat similar to RSA cryptography. But this asymmetric design does not fit the symmetric operation of Signal double ratchet algorithm, where both sides need to generate random public keys and to compute new shared secrets every time messaging direction changes for them. So to achieve that symmetry we had to use two KEM key agreements running in parallel, in a lock-step fashion, as shown on the diagram. In this case both parties generate random public keys and also use the public key of another party to encapsulate the random shared secret. Effectively, this design adds a double quantum-resistant key agreement to double ratchet algorithm steps that provide break-in recovery. + +## When can you start using quantum resistant chats? + +<img src="./images/20240314-pq1.png" width="288"> <img src="./images/20240314-pq2.png" width="288"> <img src="./images/20240314-pq3.png" width="288"> + +Quantum resistant double ratchet algorithm is already available in v5.6 (beta) of SimpleX Chat as an optional feature that can be enabled for the new and, separately, for the existing direct conversations. + +The reason it is released as opt-in is because once the conversation is upgraded to be quantum resistant, it will no longer work in the previous version of the app, and we see this ability to downgrade the app if something is not working correctly as very important for the users who use the app for critical communications. + +**To enable quantum resistance for the new conversations**: +- open the app settings (tap user avatar in the top left corner). +- scroll down to _Developer tools_ and open them. +- enable _Show developer options_ toggle. +- now you will see _Post-quantum E2EE_ toggle - enable it as well. + +Now all new contacts you add to the app will use quantum resistant Signal double ratchet algorithm. + +Once you have enabled it for the new contacts, you can also **enable it for some of the existing contacts**: +- open the chat with the contact you want to upgrade to be quantum resistant. +- tap contact name above the chat. +- tap Allow PQ encryption. +- exchange several messages back and forth with that contact - the quantum resistant double ratchet will kick in after 3-5 messages (depending on how many messages you send in each direction), and you will see the notice in the chat once it enables. + +## Next for post-quantum crypto - all direct chats, small groups and security audit + +We will be making quantum resistance default for all direct chats in v5.7, and they will be upgraded for all users without any action. + +We will also be adding quantum resistance to small groups up to 10-20 members. Computing cryptographic keys is much slower, in comparison, and it would be very inefficient (and completely unnecessary) for large public groups. + +We have also arranged a 3rd party cryptographic review of our protocol and encryption schemes design for June/July 2024 - it will cover the additions to SimpleX protocols since [the previous security audit](./20221108-simplex-chat-v4.2-security-audit-new-website.md) in November 2022, including [XFTP protocol](./20230301-simplex-file-transfer-protocol.md) we use for file transfers and quantum resistant Signal double ratchet algorithm we just released in this beta version. + +In November 2024 we will be conducting further implementation audit, with double the scope of our 2022 audit. + +Security audits are very expensive, as they require employing exceptionally competent engineers and cryptographers, and it does stretch our budgets - so any donations to help us cover the costs would be hugely helpful. + +That's it for now! + +Thank you for helping us improve the app, and look forward to your feedback. + +## 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). + +[How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions). + +Please also see our [website](https://simplex.chat). + +## Help us with donations + +Huge thank you to everybody who donates to SimpleX Chat! + +As I wrote, we are planning a 3rd party security audit for the protocols and cryptography design, and also for an app implementation, and it would hugely help us if some part of this $50,000+ expense is covered with donations. + +We are prioritizing users privacy and security - it would be impossible without your support. + +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 network based on the same principles as email and web, but much more private and secure. + +Your donations help us raise more funds – any amount, even the price of the cup of coffee, makes a big difference for us. + +See [this section](https://github.com/simplex-chat/simplex-chat/tree/master#help-us-with-donations) for the ways to donate. + +Thank you, + +Evgeny + +SimpleX Chat founder diff --git a/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md b/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md new file mode 100644 index 0000000000..f036b95f1f --- /dev/null +++ b/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.md @@ -0,0 +1,51 @@ +--- +layout: layouts/article.html +title: "SimpleX network: real privacy and stable profits, non-profit protocol governance, v5.6 released with quantum resistant e2e encryption and simple profile migration" +date: 2024-03-23 +# previewBody: blog_previews/20240314.html +preview: TODO +draft: true +# image: images/20240314-kem.jpg +# imageWide: true +permalink: "/blog/20240323-simplex-network-privacy-non-profit-v5-6-quantum-resistant-e2e-encryption-simple-migration.html" +--- + +# SimpleX network: real privacy and stable profits, non-profit protocol governance, v5.6 released with quantum resistant e2e encryption and simple profile migration + +This is a stub for release permalink + +TODO + +## 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). + +[How SimpleX is different from Session, Matrix, Signal, etc.](https://github.com/simplex-chat/simplex-chat/blob/stable/README.md#frequently-asked-questions). + +Please also see our [website](https://simplex.chat). + +## Help us with donations + +Huge thank you to everybody who donates to SimpleX Chat! + +As I wrote, we are planning a 3rd party security audit for the protocols and cryptography design, and also for an app implementation, and it would hugely help us if some part of this $50,000+ expense is covered with donations. + +We are prioritizing users privacy and security - it would be impossible without your support. + +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 network based on the same principles as email and web, but much more private and secure. + +Your donations help us raise more funds – any amount, even the price of the cup of coffee, makes a big difference for us. + +See [this section](https://github.com/simplex-chat/simplex-chat/tree/master#help-us-with-donations) for the ways to donate. + +Thank you, + +Evgeny + +SimpleX Chat founder diff --git a/blog/README.md b/blog/README.md index 8066f0592a..3b89628211 100644 --- a/blog/README.md +++ b/blog/README.md @@ -1,5 +1,21 @@ # Blog +Mar 14, 2024 [SimpleX Chat v5.6 (beta): adding quantum resistance to Signal double ratchet algorithm](./20240314-simplex-chat-v5-6-quantum-resistance-signal-double-ratchet-algorithm.md) + +This is a major upgrade for SimpleX Chat messaging protocol stack, I am really proud to present this work of the whole team. + +This post also covers various aspects of end-to-end encryption, compares different messengers, and explains how and why quantum-resistant encryption is added to SimpleX Chat: + +- Why do we need end-to-end encryption? +- Why encryption is even allowed? +- End-to-end encryption security: attacks and defense. +- How secure is encryption in different messengers? +- Adding quantum resistance to Signal double ratchet algorithm. +- When can you start using quantum resistant chats? +- Next for post-quantum crypto: all direct chats, small groups and security audit. + +--- + Jan 24, 2024 [SimpleX Chat: free infrastructure from Linode, v5.5 released](./20240124-simplex-chat-infrastructure-costs-v5-5-simplex-ux-private-notes-group-history.md) SimpleX Chat infrastructure on Linode: diff --git a/blog/images/20240314-comparison.jpg b/blog/images/20240314-comparison.jpg new file mode 100644 index 0000000000..34027a43a7 Binary files /dev/null and b/blog/images/20240314-comparison.jpg differ diff --git a/blog/images/20240314-datacenter.jpg b/blog/images/20240314-datacenter.jpg new file mode 100644 index 0000000000..9bb55c47c2 Binary files /dev/null and b/blog/images/20240314-datacenter.jpg differ diff --git a/blog/images/20240314-djb.jpg b/blog/images/20240314-djb.jpg new file mode 100644 index 0000000000..33551ec365 Binary files /dev/null and b/blog/images/20240314-djb.jpg differ diff --git a/blog/images/20240314-kem.jpg b/blog/images/20240314-kem.jpg new file mode 100644 index 0000000000..97d6ffdb4f Binary files /dev/null and b/blog/images/20240314-kem.jpg differ diff --git a/blog/images/20240314-mitm1.jpg b/blog/images/20240314-mitm1.jpg new file mode 100644 index 0000000000..b82e6534bb Binary files /dev/null and b/blog/images/20240314-mitm1.jpg differ diff --git a/blog/images/20240314-mitm2.jpg b/blog/images/20240314-mitm2.jpg new file mode 100644 index 0000000000..24132f21ab Binary files /dev/null and b/blog/images/20240314-mitm2.jpg differ diff --git a/blog/images/20240314-mitm3.jpg b/blog/images/20240314-mitm3.jpg new file mode 100644 index 0000000000..a20b37765a Binary files /dev/null and b/blog/images/20240314-mitm3.jpg differ diff --git a/blog/images/20240314-mitm4.jpg b/blog/images/20240314-mitm4.jpg new file mode 100644 index 0000000000..44a3a322a1 Binary files /dev/null and b/blog/images/20240314-mitm4.jpg differ diff --git a/blog/images/20240314-pq1.png b/blog/images/20240314-pq1.png new file mode 100644 index 0000000000..f3b88f943e Binary files /dev/null and b/blog/images/20240314-pq1.png differ diff --git a/blog/images/20240314-pq2.png b/blog/images/20240314-pq2.png new file mode 100644 index 0000000000..73d3dbde42 Binary files /dev/null and b/blog/images/20240314-pq2.png differ diff --git a/blog/images/20240314-pq3.png b/blog/images/20240314-pq3.png new file mode 100644 index 0000000000..0384c09276 Binary files /dev/null and b/blog/images/20240314-pq3.png differ diff --git a/cabal.project b/cabal.project index a9c4e33d3b..be58b64283 100644 --- a/cabal.project +++ b/cabal.project @@ -12,7 +12,7 @@ constraints: zip +disable-bzip2 +disable-zstd source-repository-package type: git location: https://github.com/simplex-chat/simplexmq.git - tag: 09878959264014f676a47f986a00c0c9fe34bcf1 + tag: 8496884b42d76bd0d934e5ae481846d9c324d3c7 source-repository-package type: git diff --git a/docs/SERVER.md b/docs/SERVER.md index 00e3e0f6ee..e476c7250c 100644 --- a/docs/SERVER.md +++ b/docs/SERVER.md @@ -17,43 +17,43 @@ _Please note_: when you change the servers in the app configuration, it only aff ## Installation -0. First, install `smp-server`: +1. First, install `smp-server`: - Manual deployment (see below) - Semi-automatic deployment: - [Offical installation script](https://github.com/simplex-chat/simplexmq#using-installation-script) - [Docker container](https://github.com/simplex-chat/simplexmq#using-docker) - - [Linode StackScript](https://github.com/simplex-chat/simplexmq#deploy-smp-server-on-linode) + - [Linode Marketplace](https://www.linode.com/marketplace/apps/simplex-chat/simplex-chat/) Manual installation requires some preliminary actions: -0. Install binary: +1. Install binary: - Using offical binaries: ```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 + 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 ``` - Compiling from source: Please refer to [Build from source: Using your distribution](https://github.com/simplex-chat/simplexmq#using-your-distribution) -1. Create user and group for `smp-server`: +2. Create user and group for `smp-server`: ```sh sudo useradd -m smp ``` -2. Create necessary directories and assign permissions: +3. Create necessary directories and assign permissions: ```sh sudo mkdir -p /var/opt/simplex /etc/opt/simplex sudo chown smp:smp /var/opt/simplex /etc/opt/simplex ``` -3. Allow `smp-server` port in firewall: +4. Allow `smp-server` port in firewall: ```sh # For Ubuntu @@ -63,7 +63,7 @@ Manual installation requires some preliminary actions: sudo firewall-cmd --reload ``` -4. **Optional** — If you're using distribution with `systemd`, create `/etc/systemd/system/smp-server.service` file with the following content: +5. **Optional** — If you're using distribution with `systemd`, create `/etc/systemd/system/smp-server.service` file with the following content: ```sh [Unit] @@ -398,25 +398,82 @@ 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 + +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 + ``` + 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 \ + -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 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. diff --git a/docs/XFTP-SERVER.md b/docs/XFTP-SERVER.md index 2977ff15da..a0ad0e0cf7 100644 --- a/docs/XFTP-SERVER.md +++ b/docs/XFTP-SERVER.md @@ -24,6 +24,7 @@ XFTP is a new file transfer protocol focussed on meta-data protection - it is ba - Semi-automatic deployment: - [Offical installation script](https://github.com/simplex-chat/simplexmq#using-installation-script) - [Docker container](https://github.com/simplex-chat/simplexmq#using-docker) + - [Linode Marketplace](https://www.linode.com/marketplace/apps/simplex-chat/simplex-chat/) Manual installation requires some preliminary actions: @@ -32,7 +33,7 @@ Manual installation requires some preliminary actions: - Using offical binaries: ```sh - curl -L https://github.com/simplex-chat/simplexmq/releases/latest/download/xftp-server-ubuntu-20_04-x86-64 -o /usr/local/bin/xftp-server + curl -L https://github.com/simplex-chat/simplexmq/releases/latest/download/xftp-server-ubuntu-20_04-x86-64 -o /usr/local/bin/xftp-server && chmod +x /usr/local/bin/xftp-server ``` - Compiling from source: @@ -418,6 +419,65 @@ To import `csv` to `Grafana` one should: For further documentation, see: [CSV Data Source for Grafana - Documentation](https://grafana.github.io/grafana-csv-datasource/) + +# Updating your XFTP server + +To update your XFTP server to latest version, choose your installation method and follow the steps: + + - Manual deployment + 1. Stop the server: + ```sh + sudo systemctl stop xftp-server + ``` + 2. Update the binary: + ```sh + curl -L https://github.com/simplex-chat/simplexmq/releases/latest/download/xftp-server-ubuntu-20_04-x86-64 -o /usr/local/bin/xftp-server && chmod +x /usr/local/bin/xftp-server + ``` + 3. Start the server: + ```sh + sudo systemctl start xftp-server + ``` + + - [Offical installation script](https://github.com/simplex-chat/simplexmq#using-installation-script) + 1. Execute the followin command: + ```sh + sudo 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/xftp-server --format="\{\{.ID\}\}")) + ``` + 2. Pull latest image: + ```sh + docker pull simplexchat/xftp-server:latest + ``` + 3. Start new container: + ```sh + docker run -d \ + -p 443:443 \ + -v $HOME/simplex/xftp/config:/etc/opt/simplex-xftp:z \ + -v $HOME/simplex/xftp/logs:/var/opt/simplex-xftp:z \ + -v $HOME/simplex/xftp/files:/srv/xftp:z \ + simplexchat/xftp-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 Please see: [SMP Server: Configuring the app to use the server](./SERVER.md#configuring-the-app-to-use-the-server). diff --git a/docs/rfcs/2023-09-30-pq-double-ratchet.md b/docs/rfcs/2023-09-30-pq-double-ratchet.md index 255051320d..95e7aa3d1d 100644 --- a/docs/rfcs/2023-09-30-pq-double-ratchet.md +++ b/docs/rfcs/2023-09-30-pq-double-ratchet.md @@ -82,7 +82,7 @@ def RatchetInitAlicePQ2HE(state, SK, bob_dh_public_key, shared_hka, shared_nhkb, state.PQRs = GENERATE_PQKEM() state.PQRr = bob_pq_kem_encapsulation_key state.PQRss = random // shared secret for KEM - state.PQRenc_ss = PQKEM-ENC(state.PQRr.encaps, state.PQRss) // encapsulated additional shared secret + state.PQRct = PQKEM-ENC(state.PQRr, state.PQRss) // encapsulated additional shared secret // above added for KEM // below augments DH key agreement with PQ shared secret state.RK, state.CKs, state.NHKs = KDF_RK_HE(SK, DH(state.DHRs, state.DHRr) || state.PQRss) @@ -103,7 +103,7 @@ def RatchetInitBobPQ2HE(state, SK, bob_dh_key_pair, shared_hka, shared_nhkb, bob state.PQRs = bob_pq_kem_key_pair state.PQRr = None state.PQRss = None - state.PQRenc_ss = None + state.PQRct = None // above added for KEM state.RK = SK state.CKs = None @@ -132,10 +132,10 @@ def RatchetEncryptPQ2HE(state, plaintext, AD): // encapsulation key from PQRs and encapsulated shared secret is added to header header = HEADER_PQ2( dh = state.DHRs.public, + kem = state.PQRs.public, // added for KEM #2 + ct = state.PQRct // added for KEM #1 pn = state.PN, n = state.Ns, - encaps = state.PQRs.encaps, // added for KEM #1 - enc_ss = state.PQRenc_ss // added for KEM #2 ) enc_header = HENCRYPT(state.HKs, header) state.Ns += 1 @@ -162,6 +162,16 @@ def RatchetDecryptPQ2HE(state, enc_header, ciphertext, AD): state.Nr += 1 return DECRYPT(mk, ciphertext, CONCAT(AD, enc_header)) +// DecryptHeader is the same as in double ratchet specification +def DecryptHeader(state, enc_header): + header = HDECRYPT(state.HKr, enc_header) + if header != None: + return header, False + header = HDECRYPT(state.NHKr, enc_header) + if header != None: + return header, True + raise Error() + def DHRatchetPQ2HE(state, header): state.PN = state.Ns state.Ns = 0 @@ -170,16 +180,16 @@ def DHRatchetPQ2HE(state, header): state.HKr = state.NHKr state.DHRr = header.dh // save new encapsulation key from header - state.PQRr = header.encaps + state.PQRr = header.kem // decapsulate shared secret from header - KEM #2 - ss = PQKEM-DEC(state.PQRs.decaps, header.enc_ss) + ss = PQKEM-DEC(state.PQRs.private, header.ct) // use decapsulated shared secret with receiving ratchet state.RK, state.CKr, state.NHKr = KDF_RK_HE(state.RK, DH(state.DHRs, state.DHRr) || ss) state.DHRs = GENERATE_DH() // below is added for KEM state.PQRs = GENERATE_PQKEM() // generate new PQ key pair state.PQRss = random // shared secret for KEM - state.PQRenc_ss = PQKEM-ENC(state.PQRr.encaps, state.PQRss) // encapsulated additional shared secret KEM #1 + state.PQRct = PQKEM-ENC(state.PQRr, state.PQRss) // encapsulated additional shared secret KEM #1 // above is added for KEM // use new shared secret with sending ratchet state.RK, state.CKs, state.NHKs = KDF_RK_HE(state.RK, DH(state.DHRs, state.DHRr) || state.PQRss) @@ -201,7 +211,7 @@ The main downside is the absense of performance-efficient implementation for aar ## Implementation considerations for SimpleX Chat -As SimpleX Chat pads messages to a fixed size, using 16kb transport blocks, the size increase introduced by this scheme will not cause additional traffic in most cases. For large texts it may require additional messages to be sent. Similarly, for media previews it may require either reducing the preview size (and quality) or sending additional messages. +As SimpleX Chat pads messages to a fixed size, using 16kb transport blocks, the size increase introduced by this scheme will not cause additional traffic in most cases. For large texts it may require additional messages to be sent. Similarly, for media previews it may require either reducing the preview size (and quality), or sending additional messages, or compressing the current JSON encoding, e.g. with zstd algorithm. That might be the primary reason why this scheme was not adopted by Signal, as it would have resulted in substantial traffic growth – to the best of our knowledge, Signal messages are not padded to a fixed size. @@ -209,6 +219,8 @@ Sharing the initial keys in case of SimpleX Chat it is equivalent to sharing the It is possible to postpone sharing the encapsulation key until the first message from Alice (confirmation message in SMP protocol), the party sending connection request. The upside here is that the invitation link size would not increase. The downside is that the user profile shared in this confirmation will not be encrypted with PQ-resistant algorithm. To mitigate it, the hadnshake protocol can be modified to postpone sending the user profile until the second message from Alice (HELLO message in SMP protocol). +Another consideration is pairwise ratchets in groups. Key generation in sntrup761 is quite slow - on slow devices it can probably be as slow as 10 keys per second, so using this primitive in groups larger than 10 members would result in slow performance. An option could be not to use ratchets in groups at all, but that would result in the lack of protection in small groups that simply combine multiple devices of 1-3 people. So a better option would be to support dynamically adding and removing sntrup761 keys for pairwise ratchets in groups, which means that when sending each message a boolean flag needs to be passed whether to use PQ KEM or not. + ## Summary If chosen PQ KEM proves secure against quantum computer attacks, then the proposed augmented double ratchet will also be secure against quantum computer attack, including break-in recovery property, while keeping deniability and forward secrecy, because the [same proof](https://eprint.iacr.org/2016/1013.pdf) as for double ratchet algorithm would hold here, provided KEM is secure. diff --git a/docs/rfcs/2023-11-21-inactive-group-members.md b/docs/rfcs/2023-11-21-inactive-group-members.md index 66e58848af..12333855a8 100644 --- a/docs/rfcs/2023-11-21-inactive-group-members.md +++ b/docs/rfcs/2023-11-21-inactive-group-members.md @@ -108,3 +108,33 @@ Sending member builds messages history starting starting from requested/remember \*** Same XGrpMsgHistory protocol event could be sent by host to new members, after sending introductions. + +--- + +Update 2024-02-12: + +### Group "pings" + +Alternatively to tracking unanswered messages counts per member, which is complex and in some cases as discussed above ineffective, group members could periodically send group wide pings indicating their active presence. + +```haskell +XGrpPing :: ChatMsgEvent 'Json +``` + +Members track: + +- inactive flag (as above - set on QUOTA errors as well) +- last_snd_ts on group +- last_rcv_ts on group member + +Clients run a worker process for checking last_snd_ts in each of their groups, and send pings to groups on a periodic basis. + +- part of cleanup manager or separate process? +- on each worker step, for each group matching criteria to send ping, send ping with a random delay to reduce correlation between groups (spawn a separate thread with a random delay for each group) +- criteria for sending ping: last_snd_ts earlier than group_ping_interval ago +- configure group_ping_interval to, for example, 23 hours (so that if user opens app each day at same time client will match criteria to send pings daily) + +Clients receiving pings: + +- update last_rcv_ts +- when sending a message to group, check only for timestamp difference (no unanswered snd msg count logic as above) diff --git a/docs/rfcs/2024-02-12-database-migration.md b/docs/rfcs/2024-02-12-database-migration.md new file mode 100644 index 0000000000..7d4dcc8d01 --- /dev/null +++ b/docs/rfcs/2024-02-12-database-migration.md @@ -0,0 +1,130 @@ +# Database migration and other operations + +## Problem + +Migrating database to another device is very complex for most people - it is multi-step and error-prone. + +In addition to that, any database operation is confusing as it requires stopping chat. + +## Solution + +Let users migrate database to another device by scanning QR code. + +Simplify other database operations by removing the need to compose multiple actions, stop chat, etc. + +To support it, we already added the way to represent the file as link/QR code (by uploading file description to XFTP, and supporting "recursive" descriptions). + +There will be these actions in the Database settings (no stop/start chat toggle): + +- Export database. +- Import database. +- Migrate from another device. +- Set passphrase (or Change passphrase if it was set). +- Remove passphrase from device / Store passphrase on the device. + +Stop chat toggle will be moved to dev tools. + +Migrate to another device will be available in the top part of the settings, + + +### Database export + +Currently, it requires these steps: + +1. Open Database settings. +2. Stop chat (many users don't understand it). +3. Tap "Export database" in settings. +4. Look at the alert that says "set passphrase". +5. Tap Ok. +6. Tap Set passphrase. +7. Enter passphrase and confirm. +8. Exit back to Database settings. +9. Tap "Export database" again. +10. Choose file location and save. +11. Tap "New archive". +12. Remove exported archive. + +These steps are all very confusing, and if they were to stay as composable steps, they belong to dev tools. + +Instead we can offer these simple steps: + +1. Open Database settings. +2. Tap "Export database". +3. Alert will appear saying: "The chat will stop, and you will need to set (or verify) database passphrase. Continue?". +4. Tap "Ok". +5. Enter passphrase and confirm in the window that appears (or verify if it was already set, possibly allowing to skip this step). +7. Choose whether to save file or upload to XFTP and generate link. +8. File: choose file location and save. + Link: show upload progress and then show link to copy. +9. Alert will appear saying: "Database exported!", exported archive will be automatically removed. + +So instead of asking users to understand the required sequence of steps, we will guide them through the required process. + +### Database import + +1. Open Database settings. +2. Tap "Import database". +3. Alert will appear saying: "The chat will stop, you will import?". +4. File: choose file location and tap "Import". + Link: paste link (or scan QR code) and tap "Import". +5. Confirm to replace database. +6. Start chat automatically once imported. + +### Set or change passphrase + +1. Open Database settings. +2. Tap "Set passphrase" or "Change passphrase" (if it was set). +3. Choose - store passphrase on the device or enter it every time the app starts. + +### Remove / store passphrase from the device + +To remove: + +1. Open Database settings. +2. Tap "Remove passphrase". +3. Confirm to remove passphrase in alert. +4. Button is replaced with Store. + +To store: + +1. Open Database settings. +2. Tap "Store passphrase". +3. Enter current passphrase - it is verified. +4. Button is replaced with Remove. + +### Migrate database to / from another device + +#### User experience + +This function is the most important, and it should be available from the main section in settings, under "Use from desktop" (or under "Link from mobile" on desktop). + +On the receiving device it will be available via Database settings and also on the Onboarding screen, so users don't need to create a profile. + +The steps are: + +On the source device: +1. Tap "Migrate to another device". +2. The chat will stop showing "Stopping chat" to the user. +3. If passphrase was: + - not set: make user set it in a separate screen. + - set: make user verify it. +5. Show the screen to confirm the upload. +6. Upload progress (full screen circular progress showing the share, with the %s and total/uploaded size) will be shown. +7. Once upload is completed, show QR code (with option to copy link), instruct to tap "Migrate from another device" on the receiving device. + +On the receiving device: +2. Tap "Migrate from another device". +2. The chat will stop (if not from Onboarding) showing "Stopping chat" to the user. +4. Scan QR code (with option to paste link on desktop only). +5. Show similar download progress, but probably in reversed direction - design TBC. +6. Once download is completed, show "Replace the current database" (if not from Onboarding). +7. Once imported, start chat automatically, and once chat started show "Tap remove database on source device". + +On the source device: +1. Tap "Remove database" on the showing screen (this should also remove uploaded file). + +#### Implementation considerations + +The latest updates allow uploading and downloading XFTP files without messages. + +So to perform the above, the second instance of the chat controller will be required, that probably requires supporting additional/optional chat controller parameter in the APIs that are required for that process. diff --git a/docs/rfcs/2024-02-13-inactive-group-members-2.md b/docs/rfcs/2024-02-13-inactive-group-members-2.md new file mode 100644 index 0000000000..6f7fc2f377 --- /dev/null +++ b/docs/rfcs/2024-02-13-inactive-group-members-2.md @@ -0,0 +1,38 @@ +# Inactive group members (simplified) + +[Original doc](./2023-11-21-inactive-group-members.md) + +## Problem + +Groups traffic is higher than necessary due to sending messages to inactive group members. + +## Solution + +### Improve connection deletion + +- When leaving or deleting group, batch db operations to optimize performance. +- In agent - fix race where connection can be deleted while it has remaining pending messages. + - Current agent logic is to immediately delete connection if it has no rcv queues left. + - Simplest should be to make a smart version of `deleteConn` for this improvement, checking `snd_messages` table for remaining messages, and keep connection around in case there are. + - While this may improve delivery of group leave and delete messages, it may as well have undesirable side effects for other use cases, as any pending messages will be sent prior to deleting connection. For example, user sends several messages on bad network, decides to delete contact, messages are still delivered when user is on good network before deletion, even though this contradicts user's intent and messages hadn't left user's device at the time of deletion. Considering this race when it happens is identical to simply leaving groups by deleting app, or deleting user profile only locally, it may be a bad idea to affect regular contact deletion for this use case. + +### Track member inactivity + +- Mark members as inactive on QUOTA errors, reset as active on QCONT + - track `group_members.inactive` flag per group member + - on SMP.QUOTA error agent to notify client with ERR CONN QUOTA (new ConnectionErrorType QUOTA) + - on receiving QCONT agent to notify client (new event) + - apart from QCONT, reset on any message or receipt +- Don't send to member if inactive + - don't send only content messages (x.msg.new, etc.) and always send messages altering group state? + - or don't send any messages? +- Track number of skipped messages per member and first skipped message + - count `group_members.skipped_msg_cnt` + - only count messages of same types/criteria that are included into history + - track `group_members.skipped_first_shared_msg_id` (only content or including service messages?) +- Send XGrpMsgSkipped before next message + - check `skipped_msg_cnt` > 0 and `skipped_first_shared_msg_id` is not null to only send once, reset after sending + +```haskell +XGrpMsgSkipped :: SharedMsgId -> Int64 -> ChatMsgEvent 'Json -- from, count +``` diff --git a/docs/rfcs/2024-02-19-settings.md b/docs/rfcs/2024-02-19-settings.md new file mode 100644 index 0000000000..002e381ce2 --- /dev/null +++ b/docs/rfcs/2024-02-19-settings.md @@ -0,0 +1,60 @@ +# Migrating app settings to another device + +## Problem + +This is related to simplified database migration UX in the [previous RFC](./2024-02-12-database-migration.md). + +Currently, when database is imported after the onboarding is complete, users can configure the app prior to the import. + +Some of the settings are particularly important for privacy and security: +- SOCKS proxy settings +- Automatic image etc. downloads +- Link previews + +With the new UX, the chat will start automatically, without giving users a chance to configure the app. That means that we have to migrate settings to a new device as well, as part of the archive. + +## Solution + +There are several possible approaches: +- put settings to the database via the API +- save settings as some file with cross-platform format (e.g. JSON or YAML or properties used on desktop). + +The second approach seems much simpler than maintaining the settings in the database. + +If we save a file, then there are two options: +- native apps maintain cross-platform schemas for this file, support any JSON and parse it in a safe way (so that even invalid or incorrect JSON - e.g., array instead of object - or invalid types in some properties do not cause the failure of properties that are correct). +- this schema and type will be maintained in the core library, that will be responsible for storing and reading the settings and passing to native UI as correct record of a given type. + +The downside of the second approach is that addition of any property that needs to be migrated will have to be done on any change in either of the platforms. The downside of the first approach is that neither app platform will be self-sufficient any more, and not only iOS/Android would have to take into account code, but also each other code. + +If we go with the second approach, there will be these types: + +```haskell +data AppSettings = AppSettings + { networkConfig :: NetworkConfig, -- existing type in Haskell and all UIs + privacyConfig :: PrivacyConfig -- new type, etc. + -- ... additional properties after the initial release should be added as Maybe, as all extensions + } + +data ArchiveConfig = ArchiveConfig + { -- existing properties + archivePath :: FilePath, + disableCompression :: Maybe Bool, + parentTempDirectory :: Maybe FilePath, + -- new property + appSettings :: AppSettings + -- for export, these settings will contain the settings passed from the UI and will be saved to JSON file as simplex_v1_settings.json in the archive + -- for import, these settings will contain the defaults that will be used if some property or subproperty is missing in JSON + } + +-- importArchive :: ChatMonad m => ArchiveConfig -> m [ArchiveError] -- current type +importArchive :: ChatMonad m => ArchiveConfig -> m ArchiveImportResult -- new type + +-- | CRArchiveImported {archiveErrors :: [ArchiveError]} -- current type + | CRArchiveImported {importResult :: ArchiveImportResult} -- new type + +data ArchiveImportResult = ArchiveImportResult + { archiveErrors :: [ArchiveError], + appSettings :: Maybe AppSettings + } +``` \ No newline at end of file diff --git a/docs/rfcs/2024-02-28-pq-integration.md b/docs/rfcs/2024-02-28-pq-integration.md new file mode 100644 index 0000000000..010f8105b7 --- /dev/null +++ b/docs/rfcs/2024-02-28-pq-integration.md @@ -0,0 +1,98 @@ +# PQ integration in chat + +## Problem + +- Group size not known when joining +- Communicate intent and current state of each conversation + +## Solution + +### Group size not known when joining + +- Add to XGrpInv GroupInvitation + - pros: easy + - cons: size can change before joining, but can ignore as it's still a good estimate + +or + +- Send before introductions + - new protocol message + - XGrpIntro :: GrpIntro -> ChatMsgEvent 'Json -- (GrpIntro is a box type with Int, for possible extension) + - or put into XGrpInfo + - XGrpInfo :: GroupProfile -> GroupStats -> ChatMsgEvent 'Json -- GroupData? + - can update profile between invitation if it happened before joining + - can later add logic to "verify" stats? + - may be over-complicated until since there "supposed" use cases are out-of-scope / not planned / not known + +- What should be default if it's not known? (e.g. admin has older version) + - On -> then off when member count reaches 20? + +### Communicate intent and current state of each conversation + +- Current state items + - RCEPQEnabled (see #3845) both for direct conversation and per member (regular event items, merged in UI) + - created when PQ changes for contact/member (e.g. received from agent on MsgMeta / SENT) + - experimental toggle is planned: it doesn't affect contacts/members with already enabled PQ + - contact enabled PQ always overrides toggle (can't downgrade) + - member enabled PQ also overrides, but can downgrade if group size increases past 20 + +- New items communicating state of e2e encryption in conversation + - should be well pronounced in UI, not merged + - should always say that conversation is e2e encrypted + - in direct chats: + - reflect actual state of PQ at the time of creation + - created during connection handshake when receiving first info about PQ in MsgMeta / some other event (TBC agent api) + - will not update if state changes (e.g. upgrades), as toggle is planned to be removed, PQ can't be downgraded, all will support soon + - flag in contacts table "e2e_info_created" to only create it once? + - should create for legacy contacts or not? + - in groups: + - reflect intent (should say "PQ will be used for members who support") based on number of members (see above) + toggle + - created at the same time as feature items? race with history may be possible, but we don't observe it? need to double check or ignore + - if based on XGrpInv GroupInvitation (first option above), can create item even before joining + - also will not update (as conversation progresses and it will scroll far up anyway) even if group size changes and it's disabled + - flag in groups table "e2e_info_created" to only create it once? and state is only reflected by RCEPQEnabled items? + - or create new such item if group size increases and PQ is off / decreases and PQ is on? + - "large group" thresholds have to different for group size increasing (e.g. 20) and decreases (e.g. 15), to avoid constant switching on the border. + +- Example texts for "e2e encryption info" chat items: + - for direct conversations: + - with PQ (and also forward a couple releases when more clients have upgraded): + ``` + Messages in this conversation are end-to-end encrypted. + Post-quantum encryption is enabled. + ``` + - no PQ (experimental toggle disabled): + ``` + -//- (e2ee) + Post-quantum encryption is not enabled. [Also possibly:] Enabling post-quantum encryption in experimental settings will enable it in this conversation if your contact supports it. + ``` + - no PQ (experimental toggle enabled): + ``` + -//- + Post-quantum encryption will be enabled when your contact upgrades. + ``` + "upgrades" / "supports it" / "starts to support it" + - can be of different color, but seems unnecessary + - created once at the start of conversation + - created once for old contacts when PQ is enabled? + - for groups: + - with PQ (small group; toggle enabled or later, as above): + ``` + -//- + Post-quantum encryption will be enabled for members who support it. + ``` + can remove qualification later when most clients have upgraded + - no PQ (large group): + ``` + -//- + Post-quantum encryption is not enabled (group is too large). + ``` + - created each time group changes between small/large, or once? + - created for old groups when experimental toggle is first turned on, and first message is received? + + +- Save PQ encryption on chat items (messages)? + - in meta for direct + group rcv + - in group_snd_item_statuses for group snd? + - display in chat item details (info) + - may be overkill if aggressive upgrade strategy is planned diff --git a/package.yaml b/package.yaml index 850f337fbc..7d8af47401 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 5.5.6.0 +version: 5.6.0.4 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme diff --git a/packages/simplex-chat-webrtc/src/android/call.html b/packages/simplex-chat-webrtc/src/android/call.html index 7b51a05151..cbdf7a23a3 100644 --- a/packages/simplex-chat-webrtc/src/android/call.html +++ b/packages/simplex-chat-webrtc/src/android/call.html @@ -8,6 +8,7 @@ <body> <video id="remote-video-stream" + class="inline" autoplay playsinline poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII=" @@ -15,6 +16,7 @@ ></video> <video id="local-video-stream" + class="inline" muted autoplay playsinline diff --git a/packages/simplex-chat-webrtc/src/android/style.css b/packages/simplex-chat-webrtc/src/android/style.css index 3d2941c71e..571c420ad5 100644 --- a/packages/simplex-chat-webrtc/src/android/style.css +++ b/packages/simplex-chat-webrtc/src/android/style.css @@ -5,14 +5,14 @@ body { background-color: black; } -#remote-video-stream { +#remote-video-stream.inline { position: absolute; width: 100%; height: 100%; object-fit: cover; } -#local-video-stream { +#local-video-stream.inline { position: absolute; width: 30%; max-width: 30%; @@ -23,6 +23,20 @@ body { right: 0; } +#remote-video-stream.fullscreen { + position: absolute; + height: 100%; + width: 100%; + object-fit: cover; +} + +#local-video-stream.fullscreen { + position: absolute; + height: 100%; + width: 100%; + object-fit: cover; +} + *::-webkit-media-controls { display: none !important; -webkit-appearance: none !important; diff --git a/packages/simplex-chat-webrtc/src/call.ts b/packages/simplex-chat-webrtc/src/call.ts index 7afdbe1003..accbebb22d 100644 --- a/packages/simplex-chat-webrtc/src/call.ts +++ b/packages/simplex-chat-webrtc/src/call.ts @@ -16,6 +16,7 @@ type WCallCommand = | WCEnableMedia | WCToggleCamera | WCDescription + | WCLayout | WCEndCall type WCallResponse = @@ -31,7 +32,7 @@ type WCallResponse = | WRError | WCAcceptOffer -type WCallCommandTag = "capabilities" | "start" | "offer" | "answer" | "ice" | "media" | "camera" | "description" | "end" +type WCallCommandTag = "capabilities" | "start" | "offer" | "answer" | "ice" | "media" | "camera" | "description" | "layout" | "end" type WCallResponseTag = "capabilities" | "offer" | "answer" | "ice" | "connection" | "connected" | "end" | "ended" | "ok" | "error" @@ -45,6 +46,12 @@ enum VideoCamera { Environment = "environment", } +enum LayoutType { + Default = "default", + LocalVideo = "localVideo", + RemoteVideo = "remoteVideo", +} + interface IWCallCommand { type: WCallCommandTag } @@ -115,6 +122,11 @@ interface WCDescription extends IWCallCommand { description: string } +interface WCLayout extends IWCallCommand { + type: "layout" + layout: LayoutType +} + interface WRCapabilities extends IWCallResponse { type: "capabilities" capabilities: CallCapabilities @@ -515,6 +527,10 @@ const processCommand = (function () { localizedDescription = command.description resp = {type: "ok"} break + case "layout": + changeLayout(command.layout) + resp = {type: "ok"} + break case "end": endCall() resp = {type: "ok"} @@ -824,6 +840,29 @@ function toggleMedia(s: MediaStream, media: CallMediaType): boolean { return res } +function changeLayout(layout: LayoutType) { + const local = document.getElementById("local-video-stream")! + const remote = document.getElementById("remote-video-stream")! + switch (layout) { + case LayoutType.Default: + local.className = "inline" + remote.className = "inline" + local.style.visibility = "visible" + remote.style.visibility = "visible" + break + case LayoutType.LocalVideo: + local.className = "fullscreen" + local.style.visibility = "visible" + remote.style.visibility = "hidden" + break + case LayoutType.RemoteVideo: + remote.className = "fullscreen" + local.style.visibility = "hidden" + remote.style.visibility = "visible" + break + } +} + type TransformFrameFunc = (key: CryptoKey) => (frame: RTCEncodedVideoFrame, controller: TransformStreamDefaultController) => Promise<void> interface CallCrypto { diff --git a/packages/simplex-chat-webrtc/src/desktop/call.html b/packages/simplex-chat-webrtc/src/desktop/call.html index dd1193babf..59ca2b58b0 100644 --- a/packages/simplex-chat-webrtc/src/desktop/call.html +++ b/packages/simplex-chat-webrtc/src/desktop/call.html @@ -9,6 +9,7 @@ <body> <video id="remote-video-stream" + class="inline" autoplay playsinline poster="data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAoAAAAKCAQAAAAnOwc2AAAAEUlEQVR42mNk+M+AARiHsiAAcCIKAYwFoQ8AAAAASUVORK5CYII=" @@ -16,6 +17,7 @@ ></video> <video id="local-video-stream" + class="inline" muted autoplay playsinline diff --git a/packages/simplex-chat-webrtc/src/desktop/style.css b/packages/simplex-chat-webrtc/src/desktop/style.css index 24c31fa6f7..80aff9e472 100644 --- a/packages/simplex-chat-webrtc/src/desktop/style.css +++ b/packages/simplex-chat-webrtc/src/desktop/style.css @@ -5,14 +5,14 @@ body { background-color: black; } -#remote-video-stream { +#remote-video-stream.inline { position: absolute; width: 100%; height: 100%; object-fit: cover; } -#local-video-stream { +#local-video-stream.inline { position: absolute; width: 20%; max-width: 20%; @@ -23,6 +23,20 @@ body { right: 0; } +#remote-video-stream.fullscreen { + position: absolute; + height: 100%; + width: 100%; + object-fit: cover; +} + +#local-video-stream.fullscreen { + position: absolute; + height: 100%; + width: 100%; + object-fit: cover; +} + *::-webkit-media-controls { display: none !important; -webkit-appearance: none !important; diff --git a/scripts/android/build-android.sh b/scripts/android/build-android.sh index 7550cdb87b..174db533a3 100755 --- a/scripts/android/build-android.sh +++ b/scripts/android/build-android.sh @@ -103,7 +103,7 @@ build() { for arch in $arches; do - tag_full="$(git tag --points-at HEAD)" + tag_full="$(git tag --points-at HEAD | head -n1)" tag_version="${tag_full%%-*}" if [ "$arch" = "armv7a" ] && [ -n "$tag_full" ] ; then diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index d82e6fd7d7..1fd783a1d3 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."09878959264014f676a47f986a00c0c9fe34bcf1" = "1w64mh1hjpjxfna48z5cg65cwwqp0w027g7d4wvzlqkf0ny4r6ib"; + "https://github.com/simplex-chat/simplexmq.git"."8496884b42d76bd0d934e5ae481846d9c324d3c7" = "0c040s00zc1qg0ilsvpv54f01xz7flh8rsar2nplcs4a4gnp78qs"; "https://github.com/simplex-chat/hs-socks.git"."a30cc7a79a08d8108316094f8f2f82a0c5e1ac51" = "0yasvnr7g91k76mjkamvzab2kvlb1g5pspjyjn2fr6v83swjhj38"; "https://github.com/simplex-chat/direct-sqlcipher.git"."f814ee68b16a9447fbb467ccc8f29bdd3546bfd9" = "1ql13f4kfwkbaq7nygkxgw84213i0zm7c1a8hwvramayxl38dq5d"; "https://github.com/simplex-chat/sqlcipher-simple.git"."a46bd361a19376c5211f1058908fc0ae6bf42446" = "1z0r78d8f0812kxbgsm735qf6xx8lvaz27k1a0b4a2m0sshpd5gl"; diff --git a/simplex-chat.cabal b/simplex-chat.cabal index 58542b8a31..9caa46da55 100644 --- a/simplex-chat.cabal +++ b/simplex-chat.cabal @@ -5,7 +5,7 @@ cabal-version: 1.12 -- see: https://github.com/sol/hpack name: simplex-chat -version: 5.5.6.0 +version: 5.6.0.4 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat @@ -26,6 +26,7 @@ flag swift library exposed-modules: Simplex.Chat + Simplex.Chat.AppSettings Simplex.Chat.Archive Simplex.Chat.Bot Simplex.Chat.Bot.KnownContacts @@ -133,6 +134,10 @@ library Simplex.Chat.Migrations.M20240104_members_profile_update Simplex.Chat.Migrations.M20240115_block_member_for_all Simplex.Chat.Migrations.M20240122_indexes + Simplex.Chat.Migrations.M20240214_redirect_file_id + Simplex.Chat.Migrations.M20240222_app_settings + Simplex.Chat.Migrations.M20240226_users_restrict + Simplex.Chat.Migrations.M20240228_pq Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared @@ -148,6 +153,7 @@ library Simplex.Chat.Remote.Transport Simplex.Chat.Remote.Types Simplex.Chat.Store + Simplex.Chat.Store.AppSettings Simplex.Chat.Store.Connections Simplex.Chat.Store.Direct Simplex.Chat.Store.Files diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 957b641eb3..cdb6f90a98 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -6,6 +6,7 @@ {-# LANGUAGE MultiWayIf #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} @@ -21,6 +22,7 @@ import Control.Monad import Control.Monad.Except import Control.Monad.IO.Unlift import Control.Monad.Reader +import Crypto.Random (ChaChaDRG) import qualified Data.Aeson as J import Data.Attoparsec.ByteString.Char8 (Parser) import qualified Data.Attoparsec.ByteString.Char8 as A @@ -67,6 +69,7 @@ import Simplex.Chat.Protocol import Simplex.Chat.Remote import Simplex.Chat.Remote.Types import Simplex.Chat.Store +import Simplex.Chat.Store.AppSettings import Simplex.Chat.Store.Connections import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Files @@ -79,9 +82,10 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Util import Simplex.Chat.Util (encryptFile, shuffle) -import Simplex.FileTransfer.Client.Main (maxFileSize) +import Simplex.FileTransfer.Client.Main (maxFileSize, maxFileSizeHard) import Simplex.FileTransfer.Client.Presets (defaultXFTPServers) -import Simplex.FileTransfer.Description (ValidFileDescription) +import Simplex.FileTransfer.Description (FileDescriptionURI (..), ValidFileDescription) +import qualified Simplex.FileTransfer.Description as FD import Simplex.FileTransfer.Protocol (FileParty (..), FilePartyI) import Simplex.Messaging.Agent as Agent import Simplex.Messaging.Agent.Client (AgentStatsKey (..), SubInfo (..), agentClientStore, getAgentWorkersDetails, getAgentWorkersSummary, temporaryAgentError) @@ -97,11 +101,14 @@ import Simplex.Messaging.Client (defaultNetworkConfig) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.File as CF +import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport (..), pattern IKNoPQ, pattern IKPQOff, pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff, pattern PQSupportOn) +import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (base64P) import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType (..), EntityId, ErrorType (..), MsgBody, MsgFlags (..), NtfServer, ProtoServerWithAuth, ProtocolTypeI, SProtocolType (..), SubscriptionMode (..), UserProtocol, userProtocol) import qualified Simplex.Messaging.Protocol as SMP +import Simplex.Messaging.ServiceScheme (ServiceScheme (..)) import qualified Simplex.Messaging.TMap as TM import Simplex.Messaging.Transport.Client (defaultSocksProxy) import Simplex.Messaging.Util @@ -169,7 +176,10 @@ _defaultSMPServers = ] _defaultNtfServers :: [NtfServer] -_defaultNtfServers = ["ntf://FB-Uop7RTaZZEG0ZLD2CIaTjsPh-Fw0zFAnb7QyA8Ks=@ntf2.simplex.im,ntg7jdjy2i3qbib3sykiho3enekwiaqg3icctliqhtqcg6jmoh6cxiad.onion"] +_defaultNtfServers = + [ "ntf://KmpZNNXiVZJx_G2T7jRUmDFxWXM3OAnunz3uLT0tqAA=@ntf3.simplex.im,pxculznuryunjdvtvh6s6szmanyadumpbmvevgdpe4wk5c65unyt4yid.onion", + "ntf://CJ5o7X6fCxj2FFYRU2KuCo70y4jSqz7td2HYhLnXWbU=@ntf4.simplex.im,wtvuhdj26jwprmomnyfu5wfuq2hjkzfcc72u44vi6gdhrwxldt6xauad.onion" + ] maxImageSize :: Integer maxImageSize = 261120 * 2 -- auto-receive on mobiles @@ -200,7 +210,7 @@ newChatController ChatDatabase {chatStore, agentStore} user cfg@ChatConfig {agentConfig = aCfg, defaultServers, inlineFiles, deviceNameForRemote} - ChatOpts {coreOptions = CoreChatOpts {smpServers, xftpServers, networkConfig, logLevel, logConnections, logServerHosts, logFile, tbqSize, highlyAvailable}, deviceName, optFilesFolder, showReactions, allowInstantFiles, autoAcceptFileSize} + ChatOpts {coreOptions = CoreChatOpts {smpServers, xftpServers, networkConfig, logLevel, logConnections, logServerHosts, logFile, tbqSize, highlyAvailable}, deviceName, optFilesFolder, optTempDirectory, showReactions, allowInstantFiles, autoAcceptFileSize} backgroundMode = do let inlineFiles' = if allowInstantFiles || autoAcceptFileSize > 0 then inlineFiles else inlineFiles {sendChunks = 0, receiveInstant = False} config = cfg {logLevel, showReactions, tbqSize, subscriptionEvents = logConnections, hostEvents = logServerHosts, defaultServers = configServers, inlineFiles = inlineFiles', autoAcceptFileSize, highlyAvailable} @@ -234,8 +244,9 @@ newChatController chatActivated <- newTVarIO True showLiveItems <- newTVarIO False encryptLocalFiles <- newTVarIO False - tempDirectory <- newTVarIO Nothing + tempDirectory <- newTVarIO optTempDirectory contactMergeEnabled <- newTVarIO True + pqExperimentalEnabled <- newTVarIO PQSupportOff pure ChatController { firstTime, @@ -271,7 +282,8 @@ newChatController encryptLocalFiles, tempDirectory, logFilePath = logFile, - contactMergeEnabled + contactMergeEnabled, + pqExperimentalEnabled } where configServers :: DefaultAgentServers @@ -314,7 +326,7 @@ startChatController mainApp = do asks smpAgent >>= resumeAgentClient unless mainApp $ chatWriteVar subscriptionMode SMOnlyCreate - users <- fromRight [] <$> runExceptT (withStoreCtx' (Just "startChatController, getUsers") getUsers) + users <- fromRight [] <$> runExceptT (withStore' getUsers) restoreCalls s <- asks agentAsync readTVarIO s >>= maybe (start s users) (pure . fst) @@ -346,7 +358,7 @@ startChatController mainApp = do _ -> pure () startExpireCIs users = forM_ users $ \user -> do - ttl <- fromRight Nothing <$> runExceptT (withStoreCtx' (Just "startExpireCIs, getChatItemTTL") (`getChatItemTTL` user)) + ttl <- fromRight Nothing <$> runExceptT (withStore' (`getChatItemTTL` user)) forM_ ttl $ \_ -> do startExpireCIThread user setExpireCIFlag user True @@ -358,7 +370,7 @@ subscribeUsers onlyNeeded users = do subscribe vr us subscribe vr us' where - subscribe :: VersionRange -> [User] -> m () + subscribe :: (PQSupport -> VersionRangeChat) -> [User] -> m () subscribe vr = mapM_ $ runExceptT . subscribeUserConnections vr onlyNeeded Agent.subscribeConnections startFilesToReceive :: forall m. ChatMonad' m => [User] -> m () @@ -372,14 +384,14 @@ startFilesToReceive users = do startReceiveUserFiles :: ChatMonad m => User -> m () startReceiveUserFiles user = do - filesToReceive <- withStoreCtx' (Just "startReceiveUserFiles, getRcvFilesToReceive") (`getRcvFilesToReceive` user) + filesToReceive <- withStore' (`getRcvFilesToReceive` user) forM_ filesToReceive $ \ft -> flip catchChatError (toView . CRChatError (Just user)) $ toView =<< receiveFile' user ft Nothing Nothing restoreCalls :: ChatMonad' m => m () restoreCalls = do - savedCalls <- fromRight [] <$> runExceptT (withStoreCtx' (Just "restoreCalls, getCalls") $ \db -> getCalls db) + savedCalls <- fromRight [] <$> runExceptT (withStore' getCalls) let callsMap = M.fromList $ map (\call@Call {contactId} -> (contactId, call)) savedCalls calls <- asks currentCalls atomically $ writeTVar calls callsMap @@ -436,10 +448,11 @@ parseChatCommand = A.parseOnly chatCommandP . B.dropWhileEnd isSpace -- | Chat API commands interpreted in context of a local zone processChatCommand :: forall m. ChatMonad m => ChatCommand -> m ChatResponse -processChatCommand cmd = chatVersionRange >>= (`processChatCommand'` cmd) +processChatCommand cmd = + chatVersionRange >>= (`processChatCommand'` cmd) {-# INLINE processChatCommand #-} -processChatCommand' :: forall m. ChatMonad m => VersionRange -> ChatCommand -> m ChatResponse +processChatCommand' :: forall m. ChatMonad m => (PQSupport -> VersionRangeChat) -> ChatCommand -> m ChatResponse processChatCommand' vr = \case ShowActiveUser -> withUser' $ pure . CRActiveUser CreateActiveUser NewUser {profile, sameServers, pastTimestamp} -> do @@ -479,13 +492,13 @@ processChatCommand' vr = \case \db -> overwriteProtocolServers db user servers coupleDaysAgo t = (`addUTCTime` t) . fromInteger . negate . (+ (2 * day)) <$> randomRIO (0, day) day = 86400 - ListUsers -> CRUsersList <$> withStoreCtx' (Just "ListUsers, getUsersInfo") getUsersInfo + ListUsers -> CRUsersList <$> withStore' getUsersInfo APISetActiveUser userId' viewPwd_ -> do unlessM chatStarted $ throwChatError CEChatNotStarted user_ <- chatReadVar currentUser user' <- privateGetUser userId' validateUserPassword_ user_ user' viewPwd_ - withStoreCtx' (Just "APISetActiveUser, setActiveUser") $ \db -> setActiveUser db userId' + withStore' (`setActiveUser` userId') let user'' = user' {activeUser = True} chatWriteVar currentUser $ Just user'' pure $ CRActiveUser user'' @@ -553,7 +566,7 @@ processChatCommand' vr = \case withAgent foregroundAgent chatWriteVar chatActivated True when restoreChat $ do - users <- withStoreCtx' (Just "APIActivateChat, getUsers") getUsers + users <- withStore' getUsers void . forkIO $ subscribeUsers True users void . forkIO $ startFilesToReceive users setAllExpireCIFlags True @@ -564,7 +577,7 @@ processChatCommand' vr = \case stopRemoteCtrl withAgent (`suspendAgent` t) ok_ - ResubscribeAllConnections -> withStoreCtx' (Just "ResubscribeAllConnections, getUsers") getUsers >>= subscribeUsers False >> ok_ + ResubscribeAllConnections -> withStore' getUsers >>= subscribeUsers False >> ok_ -- has to be called before StartChat SetTempFolder tf -> do createDirectoryIfMissing True tf @@ -579,9 +592,21 @@ processChatCommand' vr = \case chatWriteVar remoteHostsFolder $ Just rf ok_ APISetEncryptLocalFiles on -> chatWriteVar encryptLocalFiles on >> ok_ - SetContactMergeEnabled onOff -> do - asks contactMergeEnabled >>= atomically . (`writeTVar` onOff) - ok_ + SetContactMergeEnabled onOff -> chatWriteVar contactMergeEnabled onOff >> ok_ + APISetPQEncryption onOff -> chatWriteVar pqExperimentalEnabled onOff >> ok_ + APISetContactPQ ctId pqEnc -> withUser $ \user -> do + ct@Contact {activeConn} <- withStore $ \db -> getContact db vr user ctId + case activeConn of + Just conn@Connection {connId, pqSupport, pqEncryption} + | pqEncryption == pqEnc -> pure $ CRContactPQAllowed user ct pqEnc + | otherwise -> do + let pqSup = PQSupport $ pqEnc == PQEncOn || pqSupport == PQSupportOn + conn' = conn {pqSupport = pqSup, pqEncryption = pqEnc} :: Connection + ct' = ct {activeConn = Just conn'} :: Contact + withStore' $ \db -> updateConnSupportPQ db connId pqSup pqEnc + pure $ CRContactPQAllowed user ct' pqEnc + Nothing -> throwChatError $ CEContactNotActive ct + SetContactPQ cName pqEnc -> withContactName cName (`APISetContactPQ` pqEnc) APIExportArchive cfg -> checkChatStopped $ exportArchive cfg >> ok_ ExportArchive -> do ts <- liftIO getCurrentTime @@ -591,8 +616,11 @@ processChatCommand' vr = \case fileErrs <- importArchive cfg setStoreChanged pure $ CRArchiveImported fileErrs + APISaveAppSettings as -> withStore' (`saveAppSettings` as) >> ok_ + APIGetAppSettings platformDefaults -> CRAppSettings <$> withStore' (`getAppSettings` platformDefaults) APIDeleteStorage -> withStoreChanged deleteStorage APIStorageEncryption cfg -> withStoreChanged $ sqlCipherExport cfg + TestStorageEncryption key -> sqlCipherTestKey key >> ok_ ExecChatStoreSQL query -> CRSQLResult <$> withStore' (`execSQL` query) ExecAgentStoreSQL query -> CRSQLResult <$> withAgent (`execAgentStoreSQL` query) SlowSQLQueries -> do @@ -614,7 +642,7 @@ processChatCommand' vr = \case APIGetChat (ChatRef cType cId) pagination search -> withUser $ \user -> case cType of -- TODO optimize queries calculating ChatStats, currently they're disabled CTDirect -> do - directChat <- withStore (\db -> getDirectChat db user cId pagination search) + directChat <- withStore (\db -> getDirectChat db vr user cId pagination search) pure $ CRApiChat user (AChat SCTDirect directChat) CTGroup -> do groupChat <- withStore (\db -> getGroupChat db vr user cId pagination search) @@ -640,7 +668,7 @@ processChatCommand' vr = \case pure $ CRChatItemInfo user aci ChatItemInfo {itemVersions, memberDeliveryStatuses} APISendMessage (ChatRef cType chatId) live itemTTL (ComposedMessage file_ quotedItemId_ mc) -> withUser $ \user -> withChatLock "sendMessage" $ case cType of CTDirect -> do - ct@Contact {contactId, contactUsed} <- withStore $ \db -> getContact db user chatId + ct@Contact {contactId, contactUsed} <- withStore $ \db -> getContact db vr user chatId assertDirectAllowed user MDSnd ct XMsgNew_ unless contactUsed $ withStore' $ \db -> updateContactUsed db user ct if isVoice mc && not (featureAllowed SCFVoice forUser ct) @@ -649,7 +677,7 @@ processChatCommand' vr = \case (fInv_, ciFile_) <- L.unzip <$> setupSndFileTransfer ct timed_ <- sndContactCITimed live ct itemTTL (msgContainer, quotedItem_) <- prepareMsg fInv_ timed_ - (msg, _) <- sendDirectContactMessage ct (XMsgNew msgContainer) + (msg, _) <- sendDirectContactMessage user ct (XMsgNew msgContainer) ci <- saveSndChatItem' user (CDDirectSnd ct) msg (CISndMsgContent mc) ciFile_ quotedItem_ timed_ live forM_ (timed_ >>= timedDeleteAt') $ startProximateTimedItemThread user (ChatRef CTDirect contactId, chatItemId' ci) @@ -706,28 +734,18 @@ processChatCommand' vr = \case CTContactConnection -> pure $ chatCmdError (Just user) "not supported" where xftpSndFileTransfer :: User -> CryptoFile -> Integer -> Int -> ContactOrGroup -> m (FileInvitation, CIFile 'MDSnd) - xftpSndFileTransfer user file@(CryptoFile filePath cfArgs) fileSize n contactOrGroup = do - let fileName = takeFileName filePath - fileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False} - fInv = xftpFileInvitation fileName fileSize fileDescr - fsFilePath <- toFSFilePath filePath - let srcFile = CryptoFile fsFilePath cfArgs - aFileId <- withAgent $ \a -> xftpSendFile a (aUserId user) srcFile (roundedFDCount n) - -- TODO CRSndFileStart event for XFTP - chSize <- asks $ fileChunkSize . config - ft@FileTransferMeta {fileId} <- withStore' $ \db -> createSndFileTransferXFTP db user contactOrGroup file fInv (AgentSndFileId aFileId) chSize - let fileSource = Just $ CryptoFile filePath cfArgs - ciFile = CIFile {fileId, fileName, fileSize, fileSource, fileStatus = CIFSSndStored, fileProtocol = FPXFTP} + xftpSndFileTransfer user file fileSize n contactOrGroup = do + (fInv, ciFile, ft) <- xftpSndFileTransfer_ user file fileSize n $ Just contactOrGroup case contactOrGroup of CGContact Contact {activeConn} -> forM_ activeConn $ \conn -> - withStore' $ \db -> createSndFTDescrXFTP db user Nothing conn ft fileDescr + withStore' $ \db -> createSndFTDescrXFTP db user Nothing conn ft dummyFileDescr CGGroup (Group _ ms) -> forM_ ms $ \m -> saveMemberFD m `catchChatError` (toView . CRChatError (Just user)) where -- we are not sending files to pending members, same as with inline files saveMemberFD m@GroupMember {activeConn = Just conn@Connection {connStatus}} = when ((connStatus == ConnReady || connStatus == ConnSndReady) && not (connDisabled conn)) $ withStore' $ - \db -> createSndFTDescrXFTP db user (Just m) conn ft fileDescr + \db -> createSndFTDescrXFTP db user (Just m) conn ft dummyFileDescr saveMemberFD _ = pure () pure (fInv, ciFile) APICreateChatItem folderId (ComposedMessage file_ quotedItemId_ mc) -> withUser $ \user -> do @@ -748,7 +766,7 @@ processChatCommand' vr = \case pure . CRNewChatItem user $ AChatItem SCTLocal SMDSnd (LocalChat nf) ci APIUpdateChatItem (ChatRef cType chatId) itemId live mc -> withUser $ \user -> withChatLock "updateChatItem" $ case cType of CTDirect -> do - ct@Contact {contactId} <- withStore $ \db -> getContact db user chatId + ct@Contact {contactId} <- withStore $ \db -> getContact db vr user chatId assertDirectAllowed user MDSnd ct XMsgUpdate_ cci <- withStore $ \db -> getDirectCIWithReactions db user ct itemId case cci of @@ -758,7 +776,7 @@ processChatCommand' vr = \case let changed = mc /= oldMC if changed || fromMaybe False itemLive then do - (SndMessage {msgId}, _) <- sendDirectContactMessage ct (XMsgUpdate itemSharedMId mc (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive)) + (SndMessage {msgId}, _) <- sendDirectContactMessage user ct (XMsgUpdate itemSharedMId mc (ttl' <$> itemTimed) (justTrue . (live &&) =<< itemLive)) ci' <- withStore' $ \db -> do currentTs <- liftIO getCurrentTime when changed $ @@ -806,12 +824,12 @@ processChatCommand' vr = \case CTContactConnection -> pure $ chatCmdError (Just user) "not supported" APIDeleteChatItem (ChatRef cType chatId) itemId mode -> withUser $ \user -> withChatLock "deleteChatItem" $ case cType of CTDirect -> do - (ct, CChatItem msgDir ci@ChatItem {meta = CIMeta {itemSharedMsgId, editable}}) <- withStore $ \db -> (,) <$> getContact db user chatId <*> getDirectChatItem db user chatId itemId + (ct, CChatItem msgDir ci@ChatItem {meta = CIMeta {itemSharedMsgId, editable}}) <- withStore $ \db -> (,) <$> getContact db vr user chatId <*> getDirectChatItem db user chatId itemId case (mode, msgDir, itemSharedMsgId, editable) of (CIDMInternal, _, _, _) -> deleteDirectCI user ct ci True False (CIDMBroadcast, SMDSnd, Just itemSharedMId, True) -> do assertDirectAllowed user MDSnd ct XMsgDel_ - (SndMessage {msgId}, _) <- sendDirectContactMessage ct (XMsgDel itemSharedMId Nothing) + (SndMessage {msgId}, _) <- sendDirectContactMessage user ct (XMsgDel itemSharedMId Nothing) if featureAllowed SCFFullDelete forUser ct then deleteDirectCI user ct ci True False else markDirectCIDeleted user ct ci msgId True =<< liftIO getCurrentTime @@ -843,7 +861,7 @@ processChatCommand' vr = \case (_, _) -> throwChatError CEInvalidChatItemDelete APIChatItemReaction (ChatRef cType chatId) itemId add reaction -> withUser $ \user -> withChatLock "chatItemReaction" $ case cType of CTDirect -> - withStore (\db -> (,) <$> getContact db user chatId <*> getDirectChatItem db user chatId itemId) >>= \case + withStore (\db -> (,) <$> getContact db vr user chatId <*> getDirectChatItem db user chatId itemId) >>= \case (ct, CChatItem md ci@ChatItem {meta = CIMeta {itemSharedMsgId = Just itemSharedMId}}) -> do unless (featureAllowed SCFReactions forUser ct) $ throwChatError (CECommandError $ "feature not allowed " <> T.unpack (chatFeatureNameText CFReactions)) @@ -851,7 +869,7 @@ processChatCommand' vr = \case throwChatError (CECommandError "reaction not allowed - chat item has no content") rs <- withStore' $ \db -> getDirectReactions db ct itemSharedMId True checkReactionAllowed rs - (SndMessage {msgId}, _) <- sendDirectContactMessage ct $ XMsgReact itemSharedMId Nothing reaction add + (SndMessage {msgId}, _) <- sendDirectContactMessage user ct $ XMsgReact itemSharedMId Nothing reaction add createdAt <- liftIO getCurrentTime reactions <- withStore' $ \db -> do setDirectReaction db ct itemSharedMId True reaction add msgId createdAt @@ -920,7 +938,7 @@ processChatCommand' vr = \case APIChatUnread (ChatRef cType chatId) unreadChat -> withUser $ \user -> case cType of CTDirect -> do withStore $ \db -> do - ct <- getContact db user chatId + ct <- getContact db vr user chatId liftIO $ updateContactUnreadChat db user ct unreadChat ok user CTGroup -> do @@ -936,14 +954,15 @@ processChatCommand' vr = \case _ -> pure $ chatCmdError (Just user) "not supported" APIDeleteChat (ChatRef cType chatId) notify -> withUser $ \user@User {userId} -> case cType of CTDirect -> do - ct <- withStore $ \db -> getContact db user chatId + ct <- withStore $ \db -> getContact db vr user chatId filesInfo <- withStore' $ \db -> getContactFileInfo db user ct withChatLock "deleteChat direct" . procCmd $ do - deleteFilesAndConns user filesInfo - when (contactReady ct && contactActive ct && notify) $ - void (sendDirectContactMessage ct XDirectDel) `catchChatError` const (pure ()) - contactConnIds <- map aConnId <$> withStore' (\db -> getContactConnections db userId ct) - deleteAgentConnectionsAsync user contactConnIds + cancelFilesInProgress user filesInfo + deleteFilesLocally filesInfo + let doSendDel = contactReady ct && contactActive ct && notify + when doSendDel $ void (sendDirectContactMessage user ct XDirectDel) `catchChatError` const (pure ()) + contactConnIds <- map aConnId <$> withStore' (\db -> getContactConnections db vr userId ct) + deleteAgentConnectionsAsync' user contactConnIds doSendDel -- functions below are called in separate transactions to prevent crashes on android -- (possibly, race condition on integrity check?) withStore' $ \db -> deleteContactConnectionsAndFiles db userId ct @@ -962,10 +981,12 @@ processChatCommand' vr = \case unless canDelete $ throwChatError $ CEGroupUserRole gInfo GROwner filesInfo <- withStore' $ \db -> getGroupFileInfo db user gInfo withChatLock "deleteChat group" . procCmd $ do - deleteFilesAndConns user filesInfo - when (memberActive membership && isOwner) . void $ sendGroupMessage' user gInfo members XGrpDel + cancelFilesInProgress user filesInfo + deleteFilesLocally filesInfo + let doSendDel = memberActive membership && isOwner + when doSendDel . void $ sendGroupMessage' user gInfo members XGrpDel deleteGroupLinkIfExists user gInfo - deleteMembersConnections user members + deleteMembersConnections' user members doSendDel updateCIGroupInvitationStatus user gInfo CIGISRejected `catchChatError` \_ -> pure () -- functions below are called in separate transactions to prevent crashes on android -- (possibly, race condition on integrity check?) @@ -973,46 +994,49 @@ processChatCommand' vr = \case withStore' $ \db -> deleteGroupItemsAndMembers db user gInfo members withStore' $ \db -> deleteGroup db user gInfo let contactIds = mapMaybe memberContactId members - deleteAgentConnectionsAsync user . concat =<< mapM deleteUnusedContact contactIds + (errs1, (errs2, connIds)) <- second unzip . partitionEithers <$> withStoreBatch (\db -> map (deleteUnusedContact db) contactIds) + let errs = errs1 <> mapMaybe (fmap ChatErrorStore) errs2 + unless (null errs) $ toView $ CRChatErrors (Just user) errs + deleteAgentConnectionsAsync user $ concat connIds pure $ CRGroupDeletedUser user gInfo where - deleteUnusedContact :: ContactId -> m [ConnId] - deleteUnusedContact contactId = - (withStore (\db -> getContact db user contactId) >>= delete) - `catchChatError` (\e -> toView (CRChatError (Just user) e) $> []) + deleteUnusedContact :: DB.Connection -> ContactId -> IO (Either ChatError (Maybe StoreError, [ConnId])) + deleteUnusedContact db contactId = runExceptT . withExceptT ChatErrorStore $ do + ct <- getContact db vr user contactId + ifM + ((directOrUsed ct ||) . isJust <$> liftIO (checkContactHasGroups db user ct)) + (pure (Nothing, [])) + (getConnections ct) where - delete ct - | directOrUsed ct = pure [] - | otherwise = - withStore' (\db -> checkContactHasGroups db user ct) >>= \case - Just _ -> pure [] - Nothing -> do - conns <- withStore' $ \db -> getContactConnections db userId ct - withStore (\db -> setContactDeleted db user ct) - `catchChatError` (toView . CRChatError (Just user)) - pure $ map aConnId conns + getConnections :: Contact -> ExceptT StoreError IO (Maybe StoreError, [ConnId]) + getConnections ct = do + conns <- liftIO $ getContactConnections db vr userId ct + e_ <- (setContactDeleted db user ct $> Nothing) `catchStoreError` (pure . Just) + pure (e_, map aConnId conns) CTLocal -> pure $ chatCmdError (Just user) "not supported" CTContactRequest -> pure $ chatCmdError (Just user) "not supported" APIClearChat (ChatRef cType chatId) -> withUser $ \user@User {userId} -> case cType of CTDirect -> do - ct <- withStore $ \db -> getContact db user chatId + ct <- withStore $ \db -> getContact db vr user chatId filesInfo <- withStore' $ \db -> getContactFileInfo db user ct - deleteFilesAndConns user filesInfo + cancelFilesInProgress user filesInfo + deleteFilesLocally filesInfo withStore' $ \db -> deleteContactCIs db user ct pure $ CRChatCleared user (AChatInfo SCTDirect $ DirectChat ct) CTGroup -> do gInfo <- withStore $ \db -> getGroupInfo db vr user chatId filesInfo <- withStore' $ \db -> getGroupFileInfo db user gInfo - deleteFilesAndConns user filesInfo + cancelFilesInProgress user filesInfo + deleteFilesLocally filesInfo withStore' $ \db -> deleteGroupCIs db user gInfo - membersToDelete <- withStore' $ \db -> getGroupMembersForExpiration db user gInfo + membersToDelete <- withStore' $ \db -> getGroupMembersForExpiration db vr user gInfo forM_ membersToDelete $ \m -> withStore' $ \db -> deleteGroupMember db user m pure $ CRChatCleared user (AChatInfo SCTGroup $ GroupChat gInfo) CTLocal -> do nf <- withStore $ \db -> getNoteFolder db user chatId filesInfo <- withStore' $ \db -> getNoteFolderFileInfo db user nf withChatLock "clearChat local" . procCmd $ do - mapM_ (deleteFile user) filesInfo + deleteFilesLocally filesInfo withStore' $ \db -> deleteNoteFolderFiles db userId nf withStore' $ \db -> deleteNoteFolderCIs db user nf pure $ CRChatCleared user (AChatInfo SCTLocal $ LocalChat nf) @@ -1035,7 +1059,7 @@ processChatCommand' vr = \case pure $ CRContactRequestRejected user cReq APISendCallInvitation contactId callType -> withUser $ \user -> do -- party initiating call - ct <- withStore $ \db -> getContact db user contactId + ct <- withStore $ \db -> getContact db vr user contactId assertDirectAllowed user MDSnd ct XCallInv_ if featureAllowed SCFCalls forUser ct then do @@ -1046,7 +1070,7 @@ processChatCommand' vr = \case dhKeyPair <- atomically $ if encryptedCall callType then Just <$> C.generateKeyPair g else pure Nothing let invitation = CallInvitation {callType, callDhPubKey = fst <$> dhKeyPair} callState = CallInvitationSent {localCallType = callType, localDhPrivKey = snd <$> dhKeyPair} - (msg, _) <- sendDirectContactMessage ct (XCallInv callId invitation) + (msg, _) <- sendDirectContactMessage user ct (XCallInv callId invitation) ci <- saveSndChatItem user (CDDirectSnd ct) msg (CISndCall CISCallPending 0) let call' = Call {contactId, callId, chatItemId = chatItemId' ci, callState, callTs = chatItemTs' ci} call_ <- atomically $ TM.lookupInsert contactId call' calls @@ -1073,7 +1097,7 @@ processChatCommand' vr = \case offer = CallOffer {callType, rtcSession, callDhPubKey} callState' = CallOfferSent {localCallType = callType, peerCallType, localCallSession = rtcSession, sharedKey} aciContent = ACIContent SMDRcv $ CIRcvCall CISCallAccepted 0 - (SndMessage {msgId}, _) <- sendDirectContactMessage ct (XCallOffer callId offer) + (SndMessage {msgId}, _) <- sendDirectContactMessage user ct (XCallOffer callId offer) withStore' $ \db -> updateDirectChatItemsRead db user contactId $ Just (chatItemId, chatItemId) updateDirectChatItemView user ct chatItemId aciContent False $ Just msgId pure $ Just call {callState = callState'} @@ -1084,28 +1108,28 @@ processChatCommand' vr = \case CallOfferReceived {localCallType, peerCallType, peerCallSession, sharedKey} -> do let callState' = CallNegotiated {localCallType, peerCallType, localCallSession = rtcSession, peerCallSession, sharedKey} aciContent = ACIContent SMDSnd $ CISndCall CISCallNegotiated 0 - (SndMessage {msgId}, _) <- sendDirectContactMessage ct (XCallAnswer callId CallAnswer {rtcSession}) + (SndMessage {msgId}, _) <- sendDirectContactMessage user ct (XCallAnswer callId CallAnswer {rtcSession}) updateDirectChatItemView user ct chatItemId aciContent False $ Just msgId pure $ Just call {callState = callState'} _ -> throwChatError . CECallState $ callStateTag callState APISendCallExtraInfo contactId rtcExtraInfo -> -- any call party - withCurrentCall contactId $ \_ ct call@Call {callId, callState} -> case callState of + withCurrentCall contactId $ \user ct call@Call {callId, callState} -> case callState of CallOfferSent {localCallType, peerCallType, localCallSession, sharedKey} -> do -- TODO update the list of ice servers in localCallSession - void . sendDirectContactMessage ct $ XCallExtra callId CallExtraInfo {rtcExtraInfo} + void . sendDirectContactMessage user ct $ XCallExtra callId CallExtraInfo {rtcExtraInfo} let callState' = CallOfferSent {localCallType, peerCallType, localCallSession, sharedKey} pure $ Just call {callState = callState'} CallNegotiated {localCallType, peerCallType, localCallSession, peerCallSession, sharedKey} -> do -- TODO update the list of ice servers in localCallSession - void . sendDirectContactMessage ct $ XCallExtra callId CallExtraInfo {rtcExtraInfo} + void . sendDirectContactMessage user ct $ XCallExtra callId CallExtraInfo {rtcExtraInfo} let callState' = CallNegotiated {localCallType, peerCallType, localCallSession, peerCallSession, sharedKey} pure $ Just call {callState = callState'} _ -> throwChatError . CECallState $ callStateTag callState APIEndCall contactId -> -- any call party withCurrentCall contactId $ \user ct call@Call {callId} -> do - (SndMessage {msgId}, _) <- sendDirectContactMessage ct (XCallEnd callId) + (SndMessage {msgId}, _) <- sendDirectContactMessage user ct (XCallEnd callId) updateCallItemStatus user ct call WCSDisconnected $ Just msgId pure Nothing APIGetCallInvitations -> withUser $ \_ -> do @@ -1119,7 +1143,7 @@ processChatCommand' vr = \case _ -> Nothing rcvCallInvitation (contactId, callTs, peerCallType, sharedKey) = runExceptT . withStore $ \db -> do user <- getUserByContactId db contactId - contact <- getContact db user contactId + contact <- getContact db vr user contactId pure RcvCallInvitation {user, contact, callType = peerCallType, sharedKey, callTs} APIGetNetworkStatuses -> withUser $ \_ -> CRNetworkStatuses Nothing . map (uncurry ConnNetworkStatus) . M.toList <$> chatReadVar connNetworkStatuses @@ -1128,11 +1152,11 @@ processChatCommand' vr = \case updateCallItemStatus user ct call receivedStatus Nothing $> Just call APIUpdateProfile userId profile -> withUserId userId (`updateProfile` profile) APISetContactPrefs contactId prefs' -> withUser $ \user -> do - ct <- withStore $ \db -> getContact db user contactId + ct <- withStore $ \db -> getContact db vr user contactId updateContactPrefs user ct prefs' APISetContactAlias contactId localAlias -> withUser $ \user@User {userId} -> do ct' <- withStore $ \db -> do - ct <- getContact db user contactId + ct <- getContact db vr user contactId liftIO $ updateContactAlias db userId ct localAlias pure $ CRContactAliasUpdated user ct' APISetConnectionAlias connId localAlias -> withUser $ \user@User {userId} -> do @@ -1173,9 +1197,8 @@ processChatCommand' vr = \case ok user SetUserProtoServers serversConfig -> withUser $ \User {userId} -> processChatCommand $ APISetUserProtoServers userId serversConfig - APITestProtoServer userId srv@(AProtoServerWithAuth p server) -> withUserId userId $ \user -> - withServerProtocol p $ - CRServerTestResult user srv <$> withAgent (\a -> testProtocolServer a (aUserId user) server) + APITestProtoServer userId srv@(AProtoServerWithAuth _ server) -> withUserId userId $ \user -> + CRServerTestResult user srv <$> withAgent (\a -> testProtocolServer a (aUserId user) server) TestProtoServer srv -> withUser $ \User {userId} -> processChatCommand $ APITestProtoServer userId srv APISetChatItemTTL userId newTTL_ -> withUserId userId $ \user -> @@ -1197,7 +1220,7 @@ processChatCommand' vr = \case SetChatItemTTL newTTL_ -> withUser' $ \User {userId} -> do processChatCommand $ APISetChatItemTTL userId newTTL_ APIGetChatItemTTL userId -> withUserId' userId $ \user -> do - ttl <- withStoreCtx' (Just "APIGetChatItemTTL, getChatItemTTL") (`getChatItemTTL` user) + ttl <- withStore' (`getChatItemTTL` user) pure $ CRChatItemTTL user ttl GetChatItemTTL -> withUser' $ \User {userId} -> do processChatCommand $ APIGetChatItemTTL userId @@ -1208,7 +1231,7 @@ processChatCommand' vr = \case APISetChatSettings (ChatRef cType chatId) chatSettings -> withUser $ \user -> case cType of CTDirect -> do ct <- withStore $ \db -> do - ct <- getContact db user chatId + ct <- getContact db vr user chatId liftIO $ updateContactSettings db user chatId chatSettings pure ct forM_ (contactConnId ct) $ \connId -> @@ -1226,13 +1249,13 @@ processChatCommand' vr = \case APISetMemberSettings gId gMemberId settings -> withUser $ \user -> do m <- withStore $ \db -> do liftIO $ updateGroupMemberSettings db user gId gMemberId settings - getGroupMember db user gId gMemberId + getGroupMember db vr user gId gMemberId let ntfOn = showMessages $ memberSettings m toggleNtf user m ntfOn ok user APIContactInfo contactId -> withUser $ \user@User {userId} -> do -- [incognito] print user's incognito profile for this contact - ct@Contact {activeConn} <- withStore $ \db -> getContact db user contactId + ct@Contact {activeConn} <- withStore $ \db -> getContact db vr user contactId incognitoProfile <- case activeConn of Nothing -> pure Nothing Just Connection {customUserProfileId} -> @@ -1243,55 +1266,55 @@ processChatCommand' vr = \case (g, s) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> liftIO (getGroupSummary db user gId) pure $ CRGroupInfo user g s APIGroupMemberInfo gId gMemberId -> withUser $ \user -> do - (g, m) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db user gId gMemberId + (g, m) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId connectionStats <- mapM (withAgent . flip getConnectionServers) (memberConnId m) pure $ CRGroupMemberInfo user g m connectionStats APISwitchContact contactId -> withUser $ \user -> do - ct <- withStore $ \db -> getContact db user contactId + ct <- withStore $ \db -> getContact db vr user contactId case contactConnId ct of Just connId -> do connectionStats <- withAgent $ \a -> switchConnectionAsync a "" connId pure $ CRContactSwitchStarted user ct connectionStats Nothing -> throwChatError $ CEContactNotActive ct APISwitchGroupMember gId gMemberId -> withUser $ \user -> do - (g, m) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db user gId gMemberId + (g, m) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId case memberConnId m of Just connId -> do connectionStats <- withAgent (\a -> switchConnectionAsync a "" connId) pure $ CRGroupMemberSwitchStarted user g m connectionStats _ -> throwChatError CEGroupMemberNotActive APIAbortSwitchContact contactId -> withUser $ \user -> do - ct <- withStore $ \db -> getContact db user contactId + ct <- withStore $ \db -> getContact db vr user contactId case contactConnId ct of Just connId -> do connectionStats <- withAgent $ \a -> abortConnectionSwitch a connId pure $ CRContactSwitchAborted user ct connectionStats Nothing -> throwChatError $ CEContactNotActive ct APIAbortSwitchGroupMember gId gMemberId -> withUser $ \user -> do - (g, m) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db user gId gMemberId + (g, m) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId case memberConnId m of Just connId -> do connectionStats <- withAgent $ \a -> abortConnectionSwitch a connId pure $ CRGroupMemberSwitchAborted user g m connectionStats _ -> throwChatError CEGroupMemberNotActive APISyncContactRatchet contactId force -> withUser $ \user -> withChatLock "syncContactRatchet" $ do - ct <- withStore $ \db -> getContact db user contactId - case contactConnId ct of - Just connId -> do - cStats@ConnectionStats {ratchetSyncState = rss} <- withAgent $ \a -> synchronizeRatchet a connId force + ct <- withStore $ \db -> getContact db vr user contactId + case contactConn ct of + Just conn@Connection {pqSupport} -> do + cStats@ConnectionStats {ratchetSyncState = rss} <- withAgent $ \a -> synchronizeRatchet a (aConnId conn) pqSupport force createInternalChatItem user (CDDirectSnd ct) (CISndConnEvent $ SCERatchetSync rss Nothing) Nothing pure $ CRContactRatchetSyncStarted user ct cStats Nothing -> throwChatError $ CEContactNotActive ct APISyncGroupMemberRatchet gId gMemberId force -> withUser $ \user -> withChatLock "syncGroupMemberRatchet" $ do - (g, m) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db user gId gMemberId + (g, m) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId case memberConnId m of Just connId -> do - cStats@ConnectionStats {ratchetSyncState = rss} <- withAgent $ \a -> synchronizeRatchet a connId force + cStats@ConnectionStats {ratchetSyncState = rss} <- withAgent $ \a -> synchronizeRatchet a connId PQSupportOff force createInternalChatItem user (CDGroupSnd g) (CISndConnEvent . SCERatchetSync rss . Just $ groupMemberRef m) Nothing pure $ CRGroupMemberRatchetSyncStarted user g m cStats _ -> throwChatError CEGroupMemberNotActive APIGetContactCode contactId -> withUser $ \user -> do - ct@Contact {activeConn} <- withStore $ \db -> getContact db user contactId + ct@Contact {activeConn} <- withStore $ \db -> getContact db vr user contactId case activeConn of Just conn@Connection {connId} -> do code <- getConnectionCode $ aConnId conn @@ -1305,7 +1328,7 @@ processChatCommand' vr = \case pure $ CRContactCode user ct' code Nothing -> throwChatError $ CEContactNotActive ct APIGetGroupMemberCode gId gMemberId -> withUser $ \user -> do - (g, m@GroupMember {activeConn}) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db user gId gMemberId + (g, m@GroupMember {activeConn}) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId case activeConn of Just conn@Connection {connId} -> do code <- getConnectionCode $ aConnId conn @@ -1319,24 +1342,24 @@ processChatCommand' vr = \case pure $ CRGroupMemberCode user g m' code _ -> throwChatError CEGroupMemberNotActive APIVerifyContact contactId code -> withUser $ \user -> do - ct@Contact {activeConn} <- withStore $ \db -> getContact db user contactId + ct@Contact {activeConn} <- withStore $ \db -> getContact db vr user contactId case activeConn of Just conn -> verifyConnectionCode user conn code Nothing -> throwChatError $ CEContactNotActive ct APIVerifyGroupMember gId gMemberId code -> withUser $ \user -> do - GroupMember {activeConn} <- withStore $ \db -> getGroupMember db user gId gMemberId + GroupMember {activeConn} <- withStore $ \db -> getGroupMember db vr user gId gMemberId case activeConn of Just conn -> verifyConnectionCode user conn code _ -> throwChatError CEGroupMemberNotActive APIEnableContact contactId -> withUser $ \user -> do - ct@Contact {activeConn} <- withStore $ \db -> getContact db user contactId + ct@Contact {activeConn} <- withStore $ \db -> getContact db vr user contactId case activeConn of Just conn -> do withStore' $ \db -> setConnectionAuthErrCounter db user conn 0 ok user Nothing -> throwChatError $ CEContactNotActive ct APIEnableGroupMember gId gMemberId -> withUser $ \user -> do - GroupMember {activeConn} <- withStore $ \db -> getGroupMember db user gId gMemberId + GroupMember {activeConn} <- withStore $ \db -> getGroupMember db vr user gId gMemberId case activeConn of Just conn -> do withStore' $ \db -> setConnectionAuthErrCounter db user conn 0 @@ -1347,7 +1370,7 @@ processChatCommand' vr = \case SetShowMemberMessages gName mName showMessages -> withUser $ \user -> do (gId, mId) <- getGroupAndMemberId user gName mName gInfo <- withStore $ \db -> getGroupInfo db vr user gId - m <- withStore $ \db -> getGroupMember db user gId mId + m <- withStore $ \db -> getGroupMember db vr user gId mId let GroupInfo {membership = GroupMember {memberRole = membershipRole}} = gInfo when (membershipRole >= GRAdmin) $ throwChatError $ CECantBlockMemberForSelf gInfo m showMessages let settings = (memberSettings m) {showMessages} @@ -1375,8 +1398,10 @@ processChatCommand' vr = \case -- [incognito] generate profile for connection incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing subMode <- chatReadVar subscriptionMode - (connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing subMode - conn <- withStore' $ \db -> createDirectConnection db user connId cReq ConnNew incognitoProfile subMode + pqSup <- chatReadVar pqExperimentalEnabled + (connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing (IKNoPQ pqSup) subMode + -- TODO PQ pass minVersion from the current range + conn <- withStore' $ \db -> createDirectConnection db user connId cReq ConnNew incognitoProfile subMode initialChatVersion pqSup pure $ CRInvitation user cReq conn AddContact incognito -> withUser $ \User {userId} -> processChatCommand $ APIAddContact userId incognito @@ -1403,10 +1428,16 @@ processChatCommand' vr = \case -- [incognito] generate profile to send incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing let profileToSend = userProfileToSend user incognitoProfile Nothing False - dm <- directMessage $ XInfo profileToSend - connId <- withAgent $ \a -> joinConnection a (aUserId user) True cReq dm subMode - conn <- withStore' $ \db -> createDirectConnection db user connId cReq ConnJoined (incognitoProfile $> profileToSend) subMode - pure $ CRSentConfirmation user conn + pqSup <- chatReadVar pqExperimentalEnabled + withAgent' (\a -> connRequestPQSupport a pqSup cReq) >>= \case + Nothing -> throwChatError CEInvalidConnReq + -- TODO PQ the error above should be CEIncompatibleConnReqVersion, also the same API should be called in Plan + Just (agentV, pqSup') -> do + let chatV = agentToChatVersion agentV + dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend + connId <- withAgent $ \a -> joinConnection a (aUserId user) True cReq dm pqSup' subMode + conn <- withStore' $ \db -> createDirectConnection db user connId cReq ConnJoined (incognitoProfile $> profileToSend) subMode chatV pqSup' + pure $ CRSentConfirmation user conn 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 @@ -1418,7 +1449,7 @@ processChatCommand' vr = \case _ -> processChatCommand $ APIConnect userId incognito aCReqUri Connect _ Nothing -> throwChatError CEInvalidConnReq APIConnectContactViaAddress userId incognito contactId -> withUserId userId $ \user -> do - ct@Contact {activeConn, profile = LocalProfile {contactLink}} <- withStore $ \db -> getContact db user contactId + ct@Contact {activeConn, profile = LocalProfile {contactLink}} <- withStore $ \db -> getContact db vr user contactId when (isJust activeConn) $ throwChatError (CECommandError "contact already has connection") case contactLink of Just cReq -> connectContactViaAddress user incognito ct cReq @@ -1434,18 +1465,19 @@ processChatCommand' vr = \case DeleteContact cName -> withContactName cName $ \ctId -> APIDeleteChat (ChatRef CTDirect ctId) True ClearContact cName -> withContactName cName $ APIClearChat . ChatRef CTDirect APIListContacts userId -> withUserId userId $ \user -> - CRContactsList user <$> withStore' (`getUserContacts` user) + CRContactsList user <$> withStore' (\db -> getUserContacts db vr user) ListContacts -> withUser $ \User {userId} -> processChatCommand $ APIListContacts userId APICreateMyAddress userId -> withUserId userId $ \user -> withChatLock "createMyAddress" . procCmd $ do subMode <- chatReadVar subscriptionMode - (connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact Nothing subMode + -- TODO v5.7 pass IPPQOn + (connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact Nothing IKPQOff subMode withStore $ \db -> createUserContactLink db user connId cReq subMode pure $ CRUserContactLinkCreated user cReq CreateMyAddress -> withUser $ \User {userId} -> processChatCommand $ APICreateMyAddress userId APIDeleteMyAddress userId -> withUserId userId $ \user@User {profile = p} -> do - conns <- withStore (`getUserAddressConnections` user) + conns <- withStore $ \db -> getUserAddressConnections db vr user withChatLock "deleteMyAddress" $ do deleteAgentConnectionsAsync user $ map aConnId conns withStore' (`deleteUserAddress` user) @@ -1458,7 +1490,7 @@ processChatCommand' vr = \case DeleteMyAddress -> withUser $ \User {userId} -> processChatCommand $ APIDeleteMyAddress userId APIShowMyAddress userId -> withUserId' userId $ \user -> - CRUserContactLink user <$> withStoreCtx (Just "APIShowMyAddress, getUserAddress") (`getUserAddress` user) + CRUserContactLink user <$> withStore (`getUserAddress` user) ShowMyAddress -> withUser' $ \User {userId} -> processChatCommand $ APIShowMyAddress userId APISetProfileAddress userId False -> withUserId userId $ \user@User {profile = p} -> do @@ -1511,7 +1543,7 @@ processChatCommand' vr = \case _ -> throwChatError $ CECommandError "not supported" SendMemberContactMessage gName mName msg -> withUser $ \user -> do (gId, mId) <- getGroupAndMemberId user gName mName - m <- withStore $ \db -> getGroupMember db user gId mId + m <- withStore $ \db -> getGroupMember db vr user gId mId let mc = MCText msg case memberContactId m of Nothing -> do @@ -1530,7 +1562,7 @@ processChatCommand' vr = \case let mc = MCText msg processChatCommand . APISendMessage chatRef True Nothing $ ComposedMessage Nothing Nothing mc SendMessageBroadcast msg -> withUser $ \user -> do - contacts <- withStore' (`getUserContacts` user) + contacts <- withStore' $ \db -> getUserContacts db vr user let cts = filter (\ct -> contactReady ct && contactActive ct && directOrUsed ct) contacts ChatConfig {logLevel} <- asks config withChatLock "sendMessageBroadcast" . procCmd $ do @@ -1542,7 +1574,7 @@ processChatCommand' vr = \case sendAndCount user ll (s, f) ct = (sendToContact user ct $> (s + 1, f)) `catchChatError` \e -> when (ll <= CLLInfo) (toView $ CRChatError (Just user) e) $> (s, f + 1) sendToContact user ct = do - (sndMsg, _) <- sendDirectContactMessage ct (XMsgNew $ MCSimple (extMsgContent mc Nothing)) + (sndMsg, _) <- sendDirectContactMessage user ct (XMsgNew $ MCSimple (extMsgContent mc Nothing)) void $ saveSndChatItem user (CDDirectSnd ct) sndMsg (CISndMsgContent mc) SendMessageQuote cName (AMsgDirection msgDir) quotedMsg msg -> withUser $ \user@User {userId} -> do contactId <- withStore $ \db -> getContactIdByName db user cName @@ -1576,12 +1608,13 @@ processChatCommand' vr = \case -- [incognito] generate incognito profile for group membership incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing groupInfo <- withStore $ \db -> createNewGroup db vr gVar user gProfile incognitoProfile + createInternalChatItem user (CDGroupSnd groupInfo) (CISndGroupE2EEInfo $ E2EInfo {pqEnabled = PQEncOff}) Nothing pure $ CRGroupCreated user groupInfo NewGroup incognito gProfile -> withUser $ \User {userId} -> processChatCommand $ APINewGroup userId incognito gProfile APIAddMember groupId contactId memRole -> withUser $ \user -> withChatLock "addMember" $ do -- TODO for large groups: no need to load all members to determine if contact is a member - (group, contact) <- withStore $ \db -> (,) <$> getGroup db vr user groupId <*> getContact db user contactId + (group, contact) <- withStore $ \db -> (,) <$> getGroup db vr user groupId <*> getContact db vr user contactId assertDirectAllowed user MDSnd contact XGrpInv_ let Group gInfo members = group Contact {localDisplayName = cName} = contact @@ -1595,7 +1628,7 @@ processChatCommand' vr = \case Nothing -> do gVar <- asks random subMode <- chatReadVar subscriptionMode - (agentConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing subMode + (agentConnId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing IKPQOff subMode member <- withStore $ \db -> createNewContactMember db gVar user gInfo contact memRole agentConnId cReq subMode sendInvitation member cReq pure $ CRSentGroupInvitation user gInfo contact member @@ -1612,17 +1645,18 @@ processChatCommand' vr = \case withChatLock "joinGroup" . procCmd $ do (invitation, ct) <- withStore $ \db -> do inv@ReceivedGroupInvitation {fromMember} <- getGroupInvitation db vr user groupId - (inv,) <$> getContactViaMember db user fromMember + (inv,) <$> getContactViaMember db vr user fromMember let ReceivedGroupInvitation {fromMember, connRequest, groupInfo = g@GroupInfo {membership}} = invitation GroupMember {memberId = membershipMemId} = membership Contact {activeConn} = ct case activeConn of Just Connection {peerChatVRange} -> do subMode <- chatReadVar subscriptionMode - dm <- directMessage $ XGrpAcpt membershipMemId - agentConnId <- withAgent $ \a -> joinConnection a (aUserId user) True connRequest dm subMode + dm <- encodeConnInfo $ XGrpAcpt membershipMemId + agentConnId <- withAgent $ \a -> joinConnection a (aUserId user) True connRequest dm PQSupportOff subMode + let chatV = vr PQSupportOff `peerConnChatVersion` peerChatVRange withStore' $ \db -> do - createMemberConnection db userId fromMember agentConnId (fromJVersionRange peerChatVRange) subMode + createMemberConnection db userId fromMember agentConnId chatV peerChatVRange subMode updateGroupMemberStatus db userId fromMember GSMemAccepted updateGroupMemberStatus db userId membership GSMemAccepted updateCIGroupInvitationStatus user g CIGISAccepted `catchChatError` \_ -> pure () @@ -1644,7 +1678,7 @@ processChatCommand' vr = \case withStore' $ \db -> updateGroupMemberRole db user m memRole case mStatus of GSMemInvited -> do - withStore (\db -> (,) <$> mapM (getContact db user) memberContactId <*> liftIO (getMemberInvitation db user $ groupMemberId' m)) >>= \case + withStore (\db -> (,) <$> mapM (getContact db vr user) memberContactId <*> liftIO (getMemberInvitation db user $ groupMemberId' m)) >>= \case (Just ct, Just cReq) -> sendGrpInvitation user ct gInfo (m :: GroupMember) {memberRole = memRole} cReq _ -> throwChatError $ CEGroupCantResendInvitation gInfo cName _ -> do @@ -1670,7 +1704,7 @@ processChatCommand' vr = \case toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) bm' <- withStore $ \db -> do liftIO $ updateGroupMemberBlocked db user groupId memberId mrs - getGroupMember db user groupId memberId + getGroupMember db vr user groupId memberId toggleNtf user bm' (not blocked) pure CRMemberBlockedForAllUser {user, groupInfo = gInfo, member = bm', blocked} where @@ -1692,20 +1726,22 @@ processChatCommand' vr = \case (msg, _) <- sendGroupMessage user gInfo members $ XGrpMemDel mId ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndGroupEvent $ SGEMemberDeleted memberId (fromLocalProfile memberProfile)) toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) - deleteMemberConnection user m + deleteMemberConnection' user m True -- undeleted "member connected" chat item will prevent deletion of member record deleteOrUpdateMemberRecord user m pure $ CRUserDeletedMember user gInfo m {memberStatus = GSMemRemoved} APILeaveGroup groupId -> withUser $ \user@User {userId} -> do Group gInfo@GroupInfo {membership} members <- withStore $ \db -> getGroup db vr user groupId + filesInfo <- withStore' $ \db -> getGroupFileInfo db user gInfo withChatLock "leaveGroup" . procCmd $ do + cancelFilesInProgress user filesInfo (msg, _) <- sendGroupMessage' user gInfo members XGrpLeave ci <- saveSndChatItem user (CDGroupSnd gInfo) msg (CISndGroupEvent SGEUserLeft) toView $ CRNewChatItem user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) ci) -- TODO delete direct connections that were unused deleteGroupLinkIfExists user gInfo -- member records are not deleted to keep history - deleteMembersConnections user members + deleteMembersConnections' user members True withStore' $ \db -> updateGroupMemberStatus db userId membership GSMemLeft pure $ CRLeftMemberUser user gInfo {membership = membership {memberStatus = GSMemLeft}} APIListMembers groupId -> withUser $ \user -> @@ -1734,7 +1770,7 @@ processChatCommand' vr = \case APIListGroups userId contactId_ search_ -> withUserId userId $ \user -> CRGroupsList user <$> withStore' (\db -> getUserGroupsWithSummary db vr user contactId_ search_) ListGroups cName_ search_ -> withUser $ \user@User {userId} -> do - ct_ <- forM cName_ $ \cName -> withStore $ \db -> getContactByName db user cName + ct_ <- forM cName_ $ \cName -> withStore $ \db -> getContactByName db vr user cName processChatCommand $ APIListGroups userId (contactId' <$> ct_) search_ APIUpdateGroupProfile groupId p' -> withUser $ \user -> do g <- withStore $ \db -> getGroup db vr user groupId @@ -1754,7 +1790,7 @@ processChatCommand' vr = \case groupLinkId <- GroupLinkId <$> drgRandomBytes 16 subMode <- chatReadVar subscriptionMode let crClientData = encodeJSON $ CRDataGroup groupLinkId - (connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact (Just crClientData) subMode + (connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMContact (Just crClientData) IKPQOff subMode withStore $ \db -> createGroupLink db user gInfo connId cReq groupLinkId mRole subMode pure $ CRGroupLinkCreated user gInfo cReq mRole APIGroupLinkMemberRole groupId mRole' -> withUser $ \user -> withChatLock "groupLinkMemberRole " $ do @@ -1773,15 +1809,15 @@ processChatCommand' vr = \case (_, groupLink, mRole) <- withStore $ \db -> getGroupLink db user gInfo pure $ CRGroupLink user gInfo groupLink mRole APICreateMemberContact gId gMemberId -> withUser $ \user -> do - (g, m) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db user gId gMemberId + (g, m) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> getGroupMember db vr user gId gMemberId assertUserGroupRole g GRAuthor unless (groupFeatureAllowed SGFDirectMessages g) $ throwChatError $ CECommandError "direct messages not allowed" case memberConn m of Just mConn@Connection {peerChatVRange} -> do - unless (isCompatibleRange (fromJVersionRange peerChatVRange) xGrpDirectInvVRange) $ throwChatError CEPeerChatVRangeIncompatible + unless (maxVersion peerChatVRange >= groupDirectInvVersion) $ throwChatError CEPeerChatVRangeIncompatible when (isJust $ memberContactId m) $ throwChatError $ CECommandError "member contact already exists" subMode <- chatReadVar subscriptionMode - (connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing subMode + (connId, cReq) <- withAgent $ \a -> createConnection a (aUserId user) True SCMInvitation Nothing IKPQOff subMode -- [incognito] reuse membership incognito profile ct <- withStore' $ \db -> createMemberContact db user connId cReq g m mConn subMode -- TODO not sure it is correct to set connections status here? @@ -1794,7 +1830,7 @@ processChatCommand' vr = \case case memberConn m of Just mConn -> do let msg = XGrpDirectInv cReq msgContent_ - (sndMsg, _) <- sendDirectMessage mConn msg $ GroupId groupId + (sndMsg, _, _) <- sendDirectMemberMessage mConn msg groupId withStore' $ \db -> setContactGrpInvSent db ct True let ct' = ct {contactGrpInvSent = True} forM_ msgContent_ $ \mc -> do @@ -1893,16 +1929,16 @@ processChatCommand' vr = \case | otherwise -> do fileAgentConnIds <- cancelSndFile user ftm fts True deleteAgentConnectionsAsync user fileAgentConnIds - sharedMsgId <- withStore $ \db -> getSharedMsgIdByFileId db userId fileId - withStore (\db -> getChatRefByFileId db user fileId) >>= \case - ChatRef CTDirect contactId -> do - contact <- withStore $ \db -> getContact db user contactId - void . sendDirectContactMessage contact $ XFileCancel sharedMsgId - ChatRef CTGroup groupId -> do - Group gInfo ms <- withStore $ \db -> getGroup db vr user groupId + withStore (\db -> liftIO $ lookupChatRefByFileId db user fileId) >>= \case + Nothing -> pure () + Just (ChatRef CTDirect contactId) -> do + (contact, sharedMsgId) <- withStore $ \db -> (,) <$> getContact db vr user contactId <*> getSharedMsgIdByFileId db userId fileId + void . sendDirectContactMessage user contact $ XFileCancel sharedMsgId + Just (ChatRef CTGroup groupId) -> do + (Group gInfo ms, sharedMsgId) <- withStore $ \db -> (,) <$> getGroup db vr user groupId <*> getSharedMsgIdByFileId db userId fileId void . sendGroupMessage user gInfo ms $ XFileCancel sharedMsgId - _ -> throwChatError $ CEFileInternal "invalid chat ref for file transfer" - ci <- withStore $ \db -> getChatItemByFileId db vr user fileId + Just _ -> throwChatError $ CEFileInternal "invalid chat ref for file transfer" + ci <- withStore $ \db -> lookupChatItemByFileId db vr user fileId pure $ CRSndFileCancelled user ci ftm fts where fileCancelledOrCompleteSMP SndFileTransfer {fileStatus = s} = @@ -1913,7 +1949,7 @@ processChatCommand' vr = \case | otherwise -> case xftpRcvFile of Nothing -> do cancelRcvFileTransfer user ftr >>= mapM_ (deleteAgentConnectionAsync user) - ci <- withStore $ \db -> getChatItemByFileId db vr user fileId + ci <- withStore $ \db -> lookupChatItemByFileId db vr user fileId pure $ CRRcvFileCancelled user ci ftr Just XFTPRcvFile {agentRcvFileId} -> do forM_ (liveRcvFileTransferPath ftr) $ \filePath -> do @@ -1926,18 +1962,21 @@ processChatCommand' vr = \case updateCIFileStatus db user fileId CIFSRcvInvitation updateRcvFileStatus db fileId FSNew updateRcvFileAgentId db fileId Nothing - getChatItemByFileId db vr user fileId + lookupChatItemByFileId db vr user fileId pure $ CRRcvFileCancelled user ci ftr FileStatus fileId -> withUser $ \user -> do - ci@(AChatItem _ _ _ ChatItem {file}) <- withStore $ \db -> getChatItemByFileId db vr user fileId - case file of - Just CIFile {fileProtocol = FPLocal} -> - throwChatError $ CECommandError "not supported for local files" - Just CIFile {fileProtocol = FPXFTP} -> - pure $ CRFileTransferStatusXFTP user ci - _ -> do + withStore (\db -> lookupChatItemByFileId db vr user fileId) >>= \case + Nothing -> do fileStatus <- withStore $ \db -> getFileTransferProgress db user fileId pure $ CRFileTransferStatus user fileStatus + Just ci@(AChatItem _ _ _ ChatItem {file}) -> case file of + Just CIFile {fileProtocol = FPLocal} -> + throwChatError $ CECommandError "not supported for local files" + Just CIFile {fileProtocol = FPXFTP} -> + pure $ CRFileTransferStatusXFTP user ci + _ -> do + fileStatus <- withStore $ \db -> getFileTransferProgress db user fileId + pure $ CRFileTransferStatus user fileStatus ShowProfile -> withUser $ \user@User {profile} -> pure $ CRUserProfile user (fromLocalProfile profile) UpdateProfile displayName fullName -> withUser $ \user@User {profile} -> do let p = (fromLocalProfile profile :: Profile) {displayName = displayName, fullName = fullName} @@ -1950,7 +1989,7 @@ processChatCommand' vr = \case let p = (fromLocalProfile profile :: Profile) {preferences = Just . setPreference f (Just allowed) $ preferences' user} updateProfile user p SetContactFeature (ACF f) cName allowed_ -> withUser $ \user -> do - ct@Contact {userPreferences} <- withStore $ \db -> getContactByName db user cName + ct@Contact {userPreferences} <- withStore $ \db -> getContactByName db vr user cName let prefs' = setPreference f allowed_ $ Just userPreferences updateContactPrefs user ct prefs' SetGroupFeature (AGF f) gName enabled -> @@ -1962,7 +2001,7 @@ processChatCommand' vr = \case p = (fromLocalProfile profile :: Profile) {preferences = Just . setPreference' SCFTimedMessages (Just pref) $ preferences' user} updateProfile user p SetContactTimedMessages cName timedMessagesEnabled_ -> withUser $ \user -> do - ct@Contact {userPreferences = userPreferences@Preferences {timedMessages}} <- withStore $ \db -> getContactByName db user cName + ct@Contact {userPreferences = userPreferences@Preferences {timedMessages}} <- withStore $ \db -> getContactByName db vr user cName let currentTTL = timedMessages >>= \TimedMessagesPreference {ttl} -> ttl pref_ = tmeToPref currentTTL <$> timedMessagesEnabled_ prefs' = setPreference' SCFTimedMessages pref_ $ Just userPreferences @@ -1992,6 +2031,16 @@ processChatCommand' vr = \case StopRemoteCtrl -> withUser_ $ stopRemoteCtrl >> ok_ ListRemoteCtrls -> withUser_ $ CRRemoteCtrlList <$> listRemoteCtrls DeleteRemoteCtrl rc -> withUser_ $ deleteRemoteCtrl rc >> ok_ + APIUploadStandaloneFile userId file@CryptoFile {filePath} -> withUserId userId $ \user -> do + fsFilePath <- toFSFilePath filePath + fileSize <- liftIO $ CF.getFileContentsSize file {filePath = fsFilePath} + when (fileSize > toInteger maxFileSizeHard) $ throwChatError $ CEFileSize filePath + (_, _, fileTransferMeta) <- xftpSndFileTransfer_ user file fileSize 1 Nothing + pure CRSndStandaloneFileCreated {user, fileTransferMeta} + APIStandaloneFileInfo FileDescriptionURI {clientData} -> pure . CRStandaloneFileInfo $ clientData >>= J.decodeStrict . encodeUtf8 + APIDownloadStandaloneFile userId uri file -> withUserId userId $ \user -> do + ft <- receiveViaURI user uri file + pure $ CRRcvStandaloneFileCreated user ft QuitChat -> liftIO exitSuccess ShowVersion -> do -- simplexmqCommitQ makes iOS builds crash m( @@ -2097,7 +2146,7 @@ processChatCommand' vr = \case case groupLinkId of -- contact address Nothing -> - withStore' (\db -> getConnReqContactXContactId db user cReqHash) >>= \case + withStore' (\db -> getConnReqContactXContactId db vr user cReqHash) >>= \case (Just contact, _) -> pure $ CRContactAlreadyExists user contact (_, xContactId_) -> procCmd $ do let randomXContactId = XContactId <$> drgRandomBytes 16 @@ -2105,7 +2154,7 @@ processChatCommand' vr = \case connect' Nothing cReqHash xContactId False -- group link Just gLinkId -> - withStore' (\db -> getConnReqContactXContactId db user cReqHash) >>= \case + withStore' (\db -> getConnReqContactXContactId db vr user cReqHash) >>= \case (Just _contact, _) -> procCmd $ do -- allow repeat contact request newXContactId <- XContactId <$> drgRandomBytes 16 @@ -2116,26 +2165,35 @@ processChatCommand' vr = \case connect' (Just gLinkId) cReqHash xContactId True where connect' groupLinkId cReqHash xContactId inGroup = do - (connId, incognitoProfile, subMode) <- requestContact user incognito cReq xContactId inGroup - conn <- withStore' $ \db -> createConnReqConnection db userId connId cReqHash xContactId incognitoProfile groupLinkId subMode + pqSup <- if inGroup then pure PQSupportOff else chatReadVar pqExperimentalEnabled + (connId, incognitoProfile, subMode, chatV) <- requestContact user incognito cReq xContactId inGroup pqSup + conn <- withStore' $ \db -> createConnReqConnection db userId connId cReqHash xContactId incognitoProfile groupLinkId subMode chatV pqSup pure $ CRSentInvitation user conn incognitoProfile connectContactViaAddress :: User -> IncognitoEnabled -> Contact -> ConnectionRequestUri 'CMContact -> m ChatResponse connectContactViaAddress user incognito ct cReq = withChatLock "connectViaContact" $ do newXContactId <- XContactId <$> drgRandomBytes 16 - (connId, incognitoProfile, subMode) <- requestContact user incognito cReq newXContactId False + pqSup <- chatReadVar pqExperimentalEnabled + (connId, incognitoProfile, subMode, chatV) <- requestContact user incognito cReq newXContactId False pqSup let cReqHash = ConnReqUriHash . C.sha256Hash $ strEncode cReq - ct' <- withStore $ \db -> createAddressContactConnection db user ct connId cReqHash newXContactId incognitoProfile subMode + ct' <- withStore $ \db -> createAddressContactConnection db vr user ct connId cReqHash newXContactId incognitoProfile subMode chatV pqSup pure $ CRSentInvitationToContact user ct' incognitoProfile - requestContact :: User -> IncognitoEnabled -> ConnectionRequestUri 'CMContact -> XContactId -> Bool -> m (ConnId, Maybe Profile, SubscriptionMode) - requestContact user incognito cReq xContactId inGroup = do + requestContact :: User -> IncognitoEnabled -> ConnectionRequestUri 'CMContact -> XContactId -> Bool -> PQSupport -> m (ConnId, Maybe Profile, SubscriptionMode, VersionChat) + requestContact user incognito cReq xContactId inGroup pqSup = do -- [incognito] generate profile to send incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing let profileToSend = userProfileToSend user incognitoProfile Nothing inGroup - dm <- directMessage (XContact profileToSend $ Just xContactId) - subMode <- chatReadVar subscriptionMode - connId <- withAgent $ \a -> joinConnection a (aUserId user) True cReq dm subMode - pure (connId, incognitoProfile, subMode) + -- 0) toggle disabled - PQSupportOff + -- 1) toggle enabled, address supports PQ (connRequestPQSupport returns Just True) - PQSupportOn, enable support with compression + -- 2) toggle enabled, address doesn't support PQ - PQSupportOn but without compression, with version range indicating support + withAgent' (\a -> connRequestPQSupport a pqSup cReq) >>= \case + Nothing -> throwChatError CEInvalidConnReq + Just (agentV, _) -> do + let chatV = agentToChatVersion agentV + dm <- encodeConnInfoPQ pqSup chatV (XContact profileToSend $ Just xContactId) + subMode <- chatReadVar subscriptionMode + connId <- withAgent $ \a -> joinConnection a (aUserId user) True cReq dm pqSup subMode + pure (connId, incognitoProfile, subMode, chatV) contactMember :: Contact -> [GroupMember] -> Maybe GroupMember contactMember Contact {contactId} = find $ \GroupMember {memberContactId = cId, memberStatus = s} -> @@ -2155,18 +2213,21 @@ processChatCommand' vr = \case | otherwise = do when (n /= n') $ checkValidName n' -- read contacts before user update to correctly merge preferences - contacts <- withStore' (`getUserContacts` user) + contacts <- withStore' $ \db -> getUserContacts db vr user user' <- updateUser asks currentUser >>= atomically . (`writeTVar` Just user') withChatLock "updateProfile" . procCmd $ do - let changedCts = foldr (addChangedProfileContact user') [] contacts - idsEvts = map ctSndMsg changedCts - msgReqs_ <- zipWith ctMsgReq changedCts <$> createSndMessages idsEvts - (errs, cts) <- partitionEithers . zipWith (second . const) changedCts <$> deliverMessagesB msgReqs_ - unless (null errs) $ toView $ CRChatErrors (Just user) errs - let changedCts' = filter (\ChangedProfileContact {ct, ct'} -> directOrUsed ct' && mergedPreferences ct' /= mergedPreferences ct) cts - createContactsSndFeatureItems user' changedCts' - let summary = + let changedCts_ = L.nonEmpty $ foldr (addChangedProfileContact user') [] contacts + summary <- case changedCts_ of + Nothing -> pure $ UserProfileUpdateSummary 0 0 [] + Just changedCts -> do + let idsEvts = L.map ctSndMsg changedCts + msgReqs_ <- L.zipWith ctMsgReq changedCts <$> createSndMessages idsEvts + (errs, cts) <- partitionEithers . L.toList . L.zipWith (second . const) changedCts <$> deliverMessagesB msgReqs_ + unless (null errs) $ toView $ CRChatErrors (Just user) errs + let changedCts' = filter (\ChangedProfileContact {ct, ct'} -> directOrUsed ct' && mergedPreferences ct' /= mergedPreferences ct) cts + createContactsSndFeatureItems user' changedCts' + pure UserProfileUpdateSummary { updateSuccesses = length cts, updateFailures = length errs, @@ -2185,11 +2246,12 @@ processChatCommand' vr = \case mergedProfile = userProfileToSend user Nothing (Just ct) False ct' = updateMergedPreferences user' ct mergedProfile' = userProfileToSend user' Nothing (Just ct') False - ctSndMsg :: ChangedProfileContact -> (ConnOrGroupId, ChatMsgEvent 'Json) - ctSndMsg ChangedProfileContact {mergedProfile', conn = Connection {connId}} = (ConnectionId connId, XInfo mergedProfile') + ctSndMsg :: ChangedProfileContact -> (ConnOrGroupId, PQSupport, ChatMsgEvent 'Json) + ctSndMsg ChangedProfileContact {mergedProfile', conn = Connection {connId, pqSupport}} = (ConnectionId connId, pqSupport, XInfo mergedProfile') ctMsgReq :: ChangedProfileContact -> Either ChatError SndMessage -> Either ChatError MsgReq - ctMsgReq ChangedProfileContact {conn} = fmap $ \SndMessage {msgId, msgBody} -> - (conn, MsgFlags {notification = hasNotification XInfo_}, msgBody, msgId) + ctMsgReq ChangedProfileContact {conn} = + fmap $ \SndMessage {msgId, msgBody} -> + (conn, MsgFlags {notification = hasNotification XInfo_}, msgBody, msgId) updateContactPrefs :: User -> Contact -> Preferences -> m ChatResponse updateContactPrefs _ ct@Contact {activeConn = Nothing} _ = throwChatError $ CEContactNotActive ct updateContactPrefs user@User {userId} ct@Contact {activeConn = Just Connection {customUserProfileId}, userPreferences = contactUserPrefs} contactUserPrefs' @@ -2202,7 +2264,7 @@ processChatCommand' vr = \case mergedProfile' = userProfileToSend user (fromLocalProfile <$> incognitoProfile) (Just ct') False when (mergedProfile' /= mergedProfile) $ withChatLock "updateProfile" $ do - void (sendDirectContactMessage ct' $ XInfo mergedProfile') `catchChatError` (toView . CRChatError (Just user)) + void (sendDirectContactMessage user ct' $ XInfo mergedProfile') `catchChatError` (toView . CRChatError (Just user)) when (directOrUsed ct') $ createSndFeatureItems user ct ct' pure $ CRContactPrefsUpdated user ct ct' runUpdateGroupProfile :: User -> Group -> GroupProfile -> m ChatResponse @@ -2244,7 +2306,7 @@ processChatCommand' vr = \case withCurrentCall ctId action = do (user, ct) <- withStore $ \db -> do user <- getUserByContactId db ctId - (user,) <$> getContact db user ctId + (user,) <$> getContact db vr user ctId calls <- asks currentCalls withChatLock "currentCall" $ atomically (TM.lookup ctId calls) >>= \case @@ -2280,10 +2342,19 @@ processChatCommand' vr = \case groupMemberId <- getGroupMemberIdByName db user groupId groupMemberName pure (groupId, groupMemberId) sendGrpInvitation :: User -> Contact -> GroupInfo -> GroupMember -> ConnReqInvitation -> m () - sendGrpInvitation user ct@Contact {localDisplayName} GroupInfo {groupId, groupProfile, membership} GroupMember {groupMemberId, memberId, memberRole = memRole} cReq = do + sendGrpInvitation user ct@Contact {localDisplayName} gInfo@GroupInfo {groupId, groupProfile, membership} GroupMember {groupMemberId, memberId, memberRole = memRole} cReq = do + currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo let GroupMember {memberRole = userRole, memberId = userMemberId} = membership - groupInv = GroupInvitation (MemberIdRole userMemberId userRole) (MemberIdRole memberId memRole) cReq groupProfile Nothing - (msg, _) <- sendDirectContactMessage ct $ XGrpInv groupInv + groupInv = + GroupInvitation + { fromMember = MemberIdRole userMemberId userRole, + invitedMember = MemberIdRole memberId memRole, + connRequest = cReq, + groupProfile, + groupLinkId = Nothing, + groupSize = Just currentMemCount + } + (msg, _) <- sendDirectContactMessage user ct $ XGrpInv groupInv let content = CISndGroupInvitation (CIGroupInvitation {groupId, groupMemberId, localDisplayName, groupProfile, status = CIGISPending}) memRole ci <- saveSndChatItem user (CDDirectSnd ct) msg content toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) @@ -2341,7 +2412,8 @@ processChatCommand' vr = \case deleteChatUser :: User -> Bool -> m ChatResponse deleteChatUser user delSMPQueues = do filesInfo <- withStore' (`getUserFileInfo` user) - forM_ filesInfo $ \fileInfo -> deleteFile user fileInfo + cancelFilesInProgress user filesInfo + deleteFilesLocally filesInfo withAgent $ \a -> deleteUser a (aUserId user) delSMPQueues withStore' (`deleteUserRecord` user) when (activeUser user) $ chatWriteVar currentUser Nothing @@ -2351,7 +2423,7 @@ processChatCommand' vr = \case (chatId, chatSettings) <- case cType of CTDirect -> withStore $ \db -> do ctId <- getContactIdByName db user name - Contact {chatSettings} <- getContact db user ctId + Contact {chatSettings} <- getContact db vr user ctId pure (ctId, chatSettings) CTGroup -> withStore $ \db -> do @@ -2378,7 +2450,7 @@ processChatCommand' vr = \case where cReqSchemas :: (ConnReqInvitation, ConnReqInvitation) cReqSchemas = - ( CRInvitationUri crData {crScheme = CRSSimplex} e2e, + ( CRInvitationUri crData {crScheme = SSSimplex} e2e, CRInvitationUri crData {crScheme = simplexChat} e2e ) connectPlan user (ACR SCMContact (CRContactUri crData)) = do @@ -2392,7 +2464,7 @@ processChatCommand' vr = \case Nothing -> withStore' (\db -> getContactConnEntityByConnReqHash db vr user cReqHashes) >>= \case Nothing -> - withStore' (\db -> getContactWithoutConnViaAddress db user cReqSchemas) >>= \case + withStore' (\db -> getContactWithoutConnViaAddress db vr user cReqSchemas) >>= \case Nothing -> pure $ CPContactAddress CAPOk Just ct -> pure $ CPContactAddress (CAPContactViaAddress ct) Just (RcvDirectMsgConnection _conn Nothing) -> pure $ CPContactAddress CAPConnectingConfirmReconnect @@ -2423,7 +2495,7 @@ processChatCommand' vr = \case where cReqSchemas :: (ConnReqContact, ConnReqContact) cReqSchemas = - ( CRContactUri crData {crScheme = CRSSimplex}, + ( CRContactUri crData {crScheme = SSSimplex}, CRContactUri crData {crScheme = simplexChat} ) cReqHashes :: (ConnReqUriHash, ConnReqUriHash) @@ -2533,7 +2605,7 @@ startExpireCIThread user@User {userId} = do expireFlags <- asks expireCIFlags atomically $ TM.lookup userId expireFlags >>= \b -> unless (b == Just True) retry waitChatStartedAndActivated - ttl <- withStoreCtx' (Just "startExpireCIThread, getChatItemTTL") (`getChatItemTTL` user) + ttl <- withStore' (`getChatItemTTL` user) forM_ ttl $ \t -> expireChatItems user t False liftIO $ threadDelay' interval @@ -2549,50 +2621,72 @@ setAllExpireCIFlags b = do keys <- M.keys <$> readTVar expireFlags forM_ keys $ \k -> TM.insert k b expireFlags -deleteFilesAndConns :: ChatMonad m => User -> [CIFileInfo] -> m () -deleteFilesAndConns user filesInfo = do - connIds <- mapM (deleteFile user) filesInfo - deleteAgentConnectionsAsync user $ concat connIds - -deleteFile :: ChatMonad m => User -> CIFileInfo -> m [ConnId] -deleteFile user fileInfo = deleteFile' user fileInfo False - -deleteFile' :: forall m. ChatMonad m => User -> CIFileInfo -> Bool -> m [ConnId] -deleteFile' user ciFileInfo@CIFileInfo {filePath} sendCancel = do - aConnIds <- cancelFile' user ciFileInfo sendCancel - forM_ filePath $ \fPath -> - deleteFileLocally fPath `catchChatError` (toView . CRChatError (Just user)) - pure aConnIds - -deleteFileLocally :: forall m. ChatMonad m => FilePath -> m () -deleteFileLocally fPath = - withFilesFolder $ \filesFolder -> liftIO $ do - let fsFilePath = filesFolder </> fPath - removeFile fsFilePath `catchAll` \_ -> - removePathForcibly fsFilePath `catchAll_` pure () +cancelFilesInProgress :: forall m. ChatMonad m => User -> [CIFileInfo] -> m () +cancelFilesInProgress user filesInfo = do + let filesInfo' = filter (not . fileEnded) filesInfo + (sfs, rfs) <- splitFTTypes <$> withStoreBatch (\db -> map (getFT db) filesInfo') + forM_ rfs $ \RcvFileTransfer {fileId} -> closeFileHandle fileId rcvFiles `catchChatError` \_ -> pure () + void . withStoreBatch' $ \db -> map (updateSndFileCancelled db) sfs + void . withStoreBatch' $ \db -> map (updateRcvFileCancelled db) rfs + let xsfIds = mapMaybe (\(FileTransferMeta {fileId, xftpSndFile}, _) -> (,fileId) <$> xftpSndFile) sfs + xrfIds = mapMaybe (\RcvFileTransfer {fileId, xftpRcvFile} -> (,fileId) <$> xftpRcvFile) rfs + agentXFTPDeleteSndFilesRemote user xsfIds + agentXFTPDeleteRcvFiles xrfIds + let smpSFConnIds = concatMap (\(ft, sfts) -> mapMaybe (smpSndFileConnId ft) sfts) sfs + smpRFConnIds = mapMaybe smpRcvFileConnId rfs + deleteAgentConnectionsAsync user smpSFConnIds + deleteAgentConnectionsAsync user smpRFConnIds where + fileEnded CIFileInfo {fileStatus} = case fileStatus of + Just (AFS _ status) -> ciFileEnded status + Nothing -> True + getFT :: DB.Connection -> CIFileInfo -> IO (Either ChatError FileTransfer) + getFT db CIFileInfo {fileId} = runExceptT . withExceptT ChatErrorStore $ getFileTransfer db user fileId + updateSndFileCancelled :: DB.Connection -> (FileTransferMeta, [SndFileTransfer]) -> IO () + updateSndFileCancelled db (FileTransferMeta {fileId}, sfts) = do + updateFileCancelled db user fileId CIFSSndCancelled + forM_ sfts updateSndFTCancelled + where + updateSndFTCancelled :: SndFileTransfer -> IO () + updateSndFTCancelled ft = unless (sndFTEnded ft) $ do + updateSndFileStatus db ft FSCancelled + deleteSndFileChunks db ft + updateRcvFileCancelled :: DB.Connection -> RcvFileTransfer -> IO () + updateRcvFileCancelled db ft@RcvFileTransfer {fileId} = do + updateFileCancelled db user fileId CIFSRcvCancelled + updateRcvFileStatus db fileId FSCancelled + deleteRcvFileChunks db ft + splitFTTypes :: [Either ChatError FileTransfer] -> ([(FileTransferMeta, [SndFileTransfer])], [RcvFileTransfer]) + splitFTTypes = foldr addFT ([], []) . rights + where + addFT f (sfs, rfs) = case f of + FTSnd ft@FileTransferMeta {cancelled} sfts | not cancelled -> ((ft, sfts) : sfs, rfs) + FTRcv ft@RcvFileTransfer {cancelled} | not cancelled -> (sfs, ft : rfs) + _ -> (sfs, rfs) + smpSndFileConnId :: FileTransferMeta -> SndFileTransfer -> Maybe ConnId + smpSndFileConnId FileTransferMeta {xftpSndFile} sft@SndFileTransfer {agentConnId = AgentConnId acId, fileInline} + | isNothing xftpSndFile && isNothing fileInline && not (sndFTEnded sft) = Just acId + | otherwise = Nothing + smpRcvFileConnId :: RcvFileTransfer -> Maybe ConnId + smpRcvFileConnId ft@RcvFileTransfer {xftpRcvFile, rcvFileInline} + | isNothing xftpRcvFile && isNothing rcvFileInline = liveRcvFileTransferConnId ft + | otherwise = Nothing + sndFTEnded SndFileTransfer {fileStatus} = fileStatus == FSCancelled || fileStatus == FSComplete + +deleteFilesLocally :: forall m. ChatMonad m => [CIFileInfo] -> m () +deleteFilesLocally files = + withFilesFolder $ \filesFolder -> + liftIO . forM_ files $ \CIFileInfo {filePath} -> + mapM_ (delete . (filesFolder </>)) filePath + where + delete :: FilePath -> IO () + delete fPath = + removeFile fPath `catchAll` \_ -> + removePathForcibly fPath `catchAll_` pure () -- perform an action only if filesFolder is set (i.e. on mobile devices) withFilesFolder :: (FilePath -> m ()) -> m () withFilesFolder action = asks filesFolder >>= readTVarIO >>= mapM_ action -cancelFile' :: forall m. ChatMonad m => User -> CIFileInfo -> Bool -> m [ConnId] -cancelFile' user CIFileInfo {fileId, fileStatus} sendCancel = - case fileStatus of - Just fStatus -> cancel' fStatus `catchChatError` (\e -> toView (CRChatError (Just user) e) $> []) - Nothing -> pure [] - where - cancel' :: ACIFileStatus -> m [ConnId] - cancel' (AFS dir status) = - if ciFileEnded status - then pure [] - else case dir of - SMDSnd -> do - (ftm@FileTransferMeta {cancelled}, fts) <- withStore (\db -> getSndFileTransfer db user fileId) - if cancelled then pure [] else cancelSndFile user ftm fts sendCancel - SMDRcv -> do - ft@RcvFileTransfer {cancelled} <- withStore (\db -> getRcvFileTransfer db user fileId) - if cancelled then pure [] else maybeToList <$> cancelRcvFileTransfer user ft - updateCallItemStatus :: ChatMonad m => User -> Contact -> Call -> WebRTCCallStatus -> Maybe MessageId -> m () updateCallItemStatus user ct Call {chatItemId} receivedStatus msgId_ = do aciContent_ <- callStatusItemContent user ct chatItemId receivedStatus @@ -2665,14 +2759,14 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI -- direct file protocol (Nothing, Just connReq) -> do subMode <- chatReadVar subscriptionMode - dm <- directMessage $ XFileAcpt fName + dm <- encodeConnInfo $ XFileAcpt fName connIds <- joinAgentConnectionAsync user True connReq dm subMode filePath <- getRcvFilePath fileId filePath_ fName True - withStoreCtx (Just "acceptFileReceive, acceptRcvFileTransfer") $ \db -> acceptRcvFileTransfer db vr user fileId connIds ConnJoined filePath subMode + withStore $ \db -> acceptRcvFileTransfer db vr user fileId connIds ConnJoined filePath subMode -- XFTP (Just XFTPRcvFile {}, _) -> do filePath <- getRcvFilePath fileId filePath_ fName False - (ci, rfd) <- withStoreCtx (Just "acceptFileReceive, xftpAcceptRcvFT ...") $ \db -> do + (ci, rfd) <- withStore $ \db -> do -- marking file as accepted and reading description in the same transaction -- to prevent race condition with appending description ci <- xftpAcceptRcvFT db vr user fileId filePath @@ -2682,16 +2776,16 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI pure ci -- group & direct file protocol _ -> do - chatRef <- withStoreCtx (Just "acceptFileReceive, getChatRefByFileId") $ \db -> getChatRefByFileId db user fileId + chatRef <- withStore $ \db -> getChatRefByFileId db user fileId case (chatRef, grpMemberId) of (ChatRef CTDirect contactId, Nothing) -> do - ct <- withStoreCtx (Just "acceptFileReceive, getContact") $ \db -> getContact db user contactId - acceptFile CFCreateConnFileInvDirect $ \msg -> void $ sendDirectContactMessage ct msg + ct <- withStore $ \db -> getContact db vr user contactId + acceptFile CFCreateConnFileInvDirect $ \msg -> void $ sendDirectContactMessage user ct msg (ChatRef CTGroup groupId, Just memId) -> do - GroupMember {activeConn} <- withStoreCtx (Just "acceptFileReceive, getGroupMember") $ \db -> getGroupMember db user groupId memId + GroupMember {activeConn} <- withStore $ \db -> getGroupMember db vr user groupId memId case activeConn of Just conn -> do - acceptFile CFCreateConnFileInvGroup $ \msg -> void $ sendDirectMessage conn msg $ GroupId groupId + acceptFile CFCreateConnFileInvGroup $ \msg -> void $ sendDirectMemberMessage conn msg groupId _ -> throwChatError $ CEFileInternal "member connection not active" _ -> throwChatError $ CEFileInternal "invalid chat ref for file transfer" where @@ -2703,7 +2797,7 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI if | inline -> do -- accepting inline - ci <- withStoreCtx (Just "acceptFile, acceptRcvInlineFT") $ \db -> acceptRcvInlineFT db vr user fileId filePath + ci <- withStore $ \db -> acceptRcvInlineFT db vr user fileId filePath sharedMsgId <- withStore $ \db -> getSharedMsgIdByFileId db userId fileId send $ XFileAcptInv sharedMsgId Nothing fName pure ci @@ -2712,7 +2806,7 @@ acceptFileReceive user@User {userId} RcvFileTransfer {fileId, xftpRcvFile, fileI -- accepting via a new connection subMode <- chatReadVar subscriptionMode connIds <- createAgentConnectionAsync user cmdFunction True SCMInvitation subMode - withStoreCtx (Just "acceptFile, acceptRcvFileTransfer") $ \db -> acceptRcvFileTransfer db vr user fileId connIds ConnNew filePath subMode + withStore $ \db -> acceptRcvFileTransfer db vr user fileId connIds ConnNew filePath subMode receiveInline :: m Bool receiveInline = do ChatConfig {fileChunkSize, inlineFiles = InlineFilesConfig {receiveChunks, offerChunks}} <- asks config @@ -2729,12 +2823,25 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} rd <- parseFileDescription fileDescrText aFileId <- withAgent $ \a -> xftpReceiveFile a (aUserId user) rd cfArgs startReceivingFile user fileId - withStoreCtx' (Just "receiveViaCompleteFD, updateRcvFileAgentId") $ \db -> updateRcvFileAgentId db fileId (Just $ AgentRcvFileId aFileId) + withStore' $ \db -> updateRcvFileAgentId db fileId (Just $ AgentRcvFileId aFileId) + +receiveViaURI :: ChatMonad m => User -> FileDescriptionURI -> CryptoFile -> m RcvFileTransfer +receiveViaURI user@User {userId} FileDescriptionURI {description} cf@CryptoFile {cryptoArgs} = do + fileId <- withStore $ \db -> createRcvStandaloneFileTransfer db userId cf fileSize chunkSize + aFileId <- withAgent $ \a -> xftpReceiveFile a (aUserId user) description cryptoArgs + withStore $ \db -> do + liftIO $ do + updateRcvFileStatus db fileId FSConnected + updateCIFileStatus db user fileId $ CIFSRcvTransfer 0 1 + updateRcvFileAgentId db fileId (Just $ AgentRcvFileId aFileId) + getRcvFileTransfer db user fileId + where + FD.ValidFileDescription FD.FileDescription {size = FD.FileSize fileSize, chunkSize = FD.FileSize chunkSize} = description startReceivingFile :: ChatMonad m => User -> FileTransferId -> m () startReceivingFile user fileId = do vr <- chatVersionRange - ci <- withStoreCtx (Just "startReceivingFile, updateRcvFileStatus ...") $ \db -> do + ci <- withStore $ \db -> do liftIO $ updateRcvFileStatus db fileId FSConnected liftIO $ updateCIFileStatus db user fileId $ CIFSRcvTransfer 0 1 getChatItemByFileId db vr user fileId @@ -2744,51 +2851,58 @@ getRcvFilePath :: forall m. ChatMonad m => FileTransferId -> Maybe FilePath -> S getRcvFilePath fileId fPath_ fn keepHandle = case fPath_ of Nothing -> chatReadVar filesFolder >>= \case - Nothing -> - getDefaultFilesFolder - >>= (`uniqueCombine` fn) - >>= createEmptyFile - Just filesFolder -> - filesFolder `uniqueCombine` fn - >>= createEmptyFile - >>= pure <$> takeFileName + Nothing -> do + defaultFolder <- getDefaultFilesFolder + fPath <- defaultFolder `uniqueCombine` fn + createEmptyFile fPath $> fPath + Just filesFolder -> do + fPath <- filesFolder `uniqueCombine` fn + createEmptyFile fPath + pure $ takeFileName fPath Just fPath -> ifM (doesDirectoryExist fPath) - (fPath `uniqueCombine` fn >>= createEmptyFile) + (createInPassedDirectory fPath) $ ifM (doesFileExist fPath) (throwChatError $ CEFileAlreadyExists fPath) - (createEmptyFile fPath) + (createEmptyFile fPath $> fPath) where - createEmptyFile :: FilePath -> m FilePath - createEmptyFile fPath = emptyFile fPath `catchThrow` (ChatError . CEFileWrite fPath . show) - emptyFile :: FilePath -> m FilePath - emptyFile fPath = do - h <- - if keepHandle - then getFileHandle fileId fPath rcvFiles AppendMode - else getTmpHandle fPath - liftIO $ B.hPut h "" >> hFlush h - pure fPath - getTmpHandle :: FilePath -> m Handle - getTmpHandle fPath = openFile fPath AppendMode `catchThrow` (ChatError . CEFileInternal . show) + createInPassedDirectory :: FilePath -> m FilePath + createInPassedDirectory fPathDir = do + fPath <- fPathDir `uniqueCombine` fn + createEmptyFile fPath $> fPath + createEmptyFile :: FilePath -> m () + createEmptyFile fPath = emptyFile `catchThrow` (ChatError . CEFileWrite fPath . show) + where + emptyFile :: m () + emptyFile + | keepHandle = do + h <- getFileHandle fileId fPath rcvFiles AppendMode + liftIO $ B.hPut h "" >> hFlush h + | otherwise = liftIO $ B.writeFile fPath "" acceptContactRequest :: ChatMonad m => User -> UserContactRequest -> Maybe IncognitoProfile -> Bool -> m Contact -acceptContactRequest user UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = cp, userContactLinkId, xContactId} incognitoProfile contactUsed = do +acceptContactRequest user UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = cp, userContactLinkId, xContactId, pqSupport} incognitoProfile contactUsed = do subMode <- chatReadVar subscriptionMode + pqSup <- chatReadVar pqExperimentalEnabled + vr <- chatVersionRange let profileToSend = profileToSendOnAccept user incognitoProfile False - dm <- directMessage $ XInfo profileToSend - acId <- withAgent $ \a -> acceptContact a True invId dm subMode - withStore' $ \db -> createAcceptedContact db user acId (fromJVersionRange cReqChatVRange) cName profileId cp userContactLinkId xContactId incognitoProfile subMode contactUsed + chatV = vr pqSup `peerConnChatVersion` cReqChatVRange + pqSup' = pqSup `CR.pqSupportAnd` pqSupport + dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend + acId <- withAgent $ \a -> acceptContact a True invId dm pqSup' subMode + withStore' $ \db -> createAcceptedContact db user acId chatV cReqChatVRange cName profileId cp userContactLinkId xContactId incognitoProfile subMode pqSup' contactUsed -acceptContactRequestAsync :: ChatMonad m => User -> UserContactRequest -> Maybe IncognitoProfile -> Bool -> m Contact -acceptContactRequestAsync user UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange, localDisplayName = cName, profileId, profile = p, userContactLinkId, xContactId} incognitoProfile contactUsed = do +acceptContactRequestAsync :: ChatMonad m => User -> UserContactRequest -> Maybe IncognitoProfile -> Bool -> PQSupport -> m Contact +acceptContactRequestAsync user 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 - (cmdId, acId) <- agentAcceptContactAsync user True invId (XInfo profileToSend) subMode + vr <- chatVersionRange + chatV <- (\pq -> vr pq `peerConnChatVersion` cReqChatVRange) <$> chatReadVar pqExperimentalEnabled + (cmdId, acId) <- agentAcceptContactAsync user True invId (XInfo profileToSend) subMode pqSup chatV withStore' $ \db -> do - ct@Contact {activeConn} <- createAcceptedContact db user acId (fromJVersionRange cReqChatVRange) cName profileId p userContactLinkId xContactId incognitoProfile subMode contactUsed + ct@Contact {activeConn} <- createAcceptedContact db user acId chatV cReqChatVRange cName profileId p userContactLinkId xContactId incognitoProfile subMode pqSup contactUsed forM_ activeConn $ \Connection {connId} -> setCommandConnId db user cmdId connId pure ct @@ -2796,19 +2910,30 @@ acceptGroupJoinRequestAsync :: ChatMonad m => User -> GroupInfo -> UserContactRe acceptGroupJoinRequestAsync user gInfo@GroupInfo {groupProfile, membership} - ucr@UserContactRequest {agentInvitationId = AgentInvId invId} + ucr@UserContactRequest {agentInvitationId = AgentInvId invId, cReqChatVRange} gLinkMemRole incognitoProfile = do gVar <- asks random (groupMemberId, memberId) <- withStore $ \db -> createAcceptedMember db gVar user gInfo ucr gLinkMemRole + currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo let Profile {displayName} = profileToSendOnAccept user incognitoProfile True GroupMember {memberRole = userRole, memberId = userMemberId} = membership - msg = XGrpLinkInv $ GroupLinkInvitation (MemberIdRole userMemberId userRole) displayName (MemberIdRole memberId gLinkMemRole) groupProfile + msg = + XGrpLinkInv $ + GroupLinkInvitation + { fromMember = MemberIdRole userMemberId userRole, + fromMemberName = displayName, + invitedMember = MemberIdRole memberId gLinkMemRole, + groupProfile, + groupSize = Just currentMemCount + } subMode <- chatReadVar subscriptionMode - connIds <- agentAcceptContactAsync user True invId msg subMode + vr <- chatVersionRange + chatV <- (\pq -> vr pq `peerConnChatVersion` cReqChatVRange) <$> chatReadVar pqExperimentalEnabled + connIds <- agentAcceptContactAsync user True invId msg subMode PQSupportOff chatV withStore $ \db -> do - liftIO $ createAcceptedMemberConnection db user connIds ucr groupMemberId subMode - getGroupMemberById db user groupMemberId + liftIO $ createAcceptedMemberConnection db user connIds chatV ucr groupMemberId subMode + getGroupMemberById db vr user groupMemberId profileToSendOnAccept :: User -> Maybe IncognitoProfile -> Bool -> Profile profileToSendOnAccept user ip = userProfileToSend user (getIncognitoProfile <$> ip) Nothing @@ -2819,12 +2944,14 @@ profileToSendOnAccept user ip = userProfileToSend user (getIncognitoProfile <$> deleteGroupLink' :: ChatMonad m => User -> GroupInfo -> m () deleteGroupLink' user gInfo = do - conn <- withStore $ \db -> getGroupLinkConnection db user gInfo + vr <- chatVersionRange + conn <- withStore $ \db -> getGroupLinkConnection db vr user gInfo deleteGroupLink_ user gInfo conn deleteGroupLinkIfExists :: ChatMonad m => User -> GroupInfo -> m () deleteGroupLinkIfExists user gInfo = do - conn_ <- eitherToMaybe <$> withStore' (\db -> runExceptT $ getGroupLinkConnection db user gInfo) + vr <- chatVersionRange + conn_ <- eitherToMaybe <$> withStore' (\db -> runExceptT $ getGroupLinkConnection db vr user gInfo) mapM_ (deleteGroupLink_ user gInfo) conn_ deleteGroupLink_ :: ChatMonad m => User -> GroupInfo -> Connection -> m () @@ -2853,8 +2980,8 @@ agentSubscriber = do type AgentBatchSubscribe m = AgentClient -> [ConnId] -> ExceptT AgentErrorType m (Map ConnId (Either AgentErrorType ())) -subscribeUserConnections :: forall m. ChatMonad m => VersionRange -> Bool -> AgentBatchSubscribe m -> User -> m () -subscribeUserConnections vr onlyNeeded agentBatchSubscribe user@User {userId} = do +subscribeUserConnections :: forall m. ChatMonad m => (PQSupport -> VersionRangeChat) -> Bool -> AgentBatchSubscribe m -> User -> m () +subscribeUserConnections vr onlyNeeded agentBatchSubscribe user = do -- get user connections ce <- asks $ subscriptionEvents . config (conns, cts, ucs, gs, ms, sfts, rfts, pcs) <- @@ -2909,32 +3036,32 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user@User {userId} = } getContactConns :: m ([ConnId], Map ConnId Contact) getContactConns = do - cts <- withStore_ ("subscribeUserConnections " <> show userId <> ", getUserContacts") getUserContacts + cts <- withStore_ (`getUserContacts` vr) let cts' = mapMaybe (\ct -> (,ct) <$> contactConnId ct) $ filter contactActive cts pure (map fst cts', M.fromList cts') getUserContactLinkConns :: m ([ConnId], Map ConnId UserContact) getUserContactLinkConns = do - (cs, ucs) <- unzip <$> withStore_ ("subscribeUserConnections " <> show userId <> ", getUserContactLinks") getUserContactLinks + (cs, ucs) <- unzip <$> withStore_ (`getUserContactLinks` vr) let connIds = map aConnId cs pure (connIds, M.fromList $ zip connIds ucs) getGroupMemberConns :: m ([Group], [ConnId], Map ConnId GroupMember) getGroupMemberConns = do - gs <- withStore_ ("subscribeUserConnections " <> show userId <> ", getUserGroups") (`getUserGroups` vr) + gs <- withStore_ (`getUserGroups` vr) let mPairs = concatMap (\(Group _ ms) -> mapMaybe (\m -> (,m) <$> memberConnId m) (filter (not . memberRemoved) ms)) gs pure (gs, map fst mPairs, M.fromList mPairs) getSndFileTransferConns :: m ([ConnId], Map ConnId SndFileTransfer) getSndFileTransferConns = do - sfts <- withStore_ ("subscribeUserConnections " <> show userId <> ", getLiveSndFileTransfers") getLiveSndFileTransfers + sfts <- withStore_ getLiveSndFileTransfers let connIds = map sndFileTransferConnId sfts pure (connIds, M.fromList $ zip connIds sfts) getRcvFileTransferConns :: m ([ConnId], Map ConnId RcvFileTransfer) getRcvFileTransferConns = do - rfts <- withStore_ ("subscribeUserConnections " <> show userId <> ", getLiveRcvFileTransfers") getLiveRcvFileTransfers + rfts <- withStore_ getLiveRcvFileTransfers let rftPairs = mapMaybe (\ft -> (,ft) <$> liveRcvFileTransferConnId ft) rfts pure (map fst rftPairs, M.fromList rftPairs) getPendingContactConns :: m ([ConnId], Map ConnId PendingContactConnection) getPendingContactConns = do - pcs <- withStore_ ("subscribeUserConnections " <> show userId <> ", getPendingContactConnections") getPendingContactConnections + pcs <- withStore_ getPendingContactConnections let connIds = map aConnId' pcs pure (connIds, M.fromList $ zip connIds pcs) contactSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId Contact -> Bool -> m () @@ -3004,8 +3131,8 @@ subscribeUserConnections vr onlyNeeded agentBatchSubscribe user@User {userId} = rcvFileSubsToView rs = mapM_ (toView . uncurry (CRRcvFileSubError user)) . filterErrors . resultsFor rs pendingConnSubsToView :: Map ConnId (Either AgentErrorType ()) -> Map ConnId PendingContactConnection -> m () pendingConnSubsToView rs = toView . CRPendingSubSummary user . map (uncurry PendingSubStatus) . resultsFor rs - withStore_ :: String -> (DB.Connection -> User -> IO [a]) -> m [a] - withStore_ ctx a = withStoreCtx' (Just ctx) (`a` user) `catchChatError` \e -> toView (CRChatError (Just user) e) $> [] + withStore_ :: (DB.Connection -> User -> IO [a]) -> m [a] + withStore_ a = withStore' (`a` user) `catchChatError` \e -> toView (CRChatError (Just user) e) $> [] filterErrors :: [(a, Maybe ChatError)] -> [(a, ChatError)] filterErrors = mapMaybe (\(a, e_) -> (a,) <$> e_) resultsFor :: Map ConnId (Either AgentErrorType ()) -> Map ConnId a -> [(a, Maybe ChatError)] @@ -3029,7 +3156,7 @@ cleanupManager = do forever $ do flip catchChatError (toView . CRChatError Nothing) $ do waitChatStartedAndActivated - users <- withStoreCtx' (Just "cleanupManager, getUsers 1") getUsers + users <- withStore' getUsers let (us, us') = partition activeUser users forM_ us $ cleanupUser interval stepDelay forM_ us' $ cleanupUser interval stepDelay @@ -3039,7 +3166,7 @@ cleanupManager = do where runWithoutInitialDelay cleanupInterval = flip catchChatError (toView . CRChatError Nothing) $ do waitChatStartedAndActivated - users <- withStoreCtx' (Just "cleanupManager, getUsers 2") getUsers + users <- withStore' getUsers let (us, us') = partition activeUser users forM_ us $ \u -> cleanupTimedItems cleanupInterval u `catchChatError` (toView . CRChatError (Just u)) forM_ us' $ \u -> cleanupTimedItems cleanupInterval u `catchChatError` (toView . CRChatError (Just u)) @@ -3051,17 +3178,18 @@ cleanupManager = do cleanupTimedItems cleanupInterval user = do ts <- liftIO getCurrentTime let startTimedThreadCutoff = addUTCTime cleanupInterval ts - timedItems <- withStoreCtx' (Just "cleanupManager, getTimedItems") $ \db -> getTimedItems db user startTimedThreadCutoff + timedItems <- withStore' $ \db -> getTimedItems db user startTimedThreadCutoff forM_ timedItems $ \(itemRef, deleteAt) -> startTimedItemThread user itemRef deleteAt `catchChatError` const (pure ()) cleanupDeletedContacts user = do - contacts <- withStore' (`getDeletedContacts` user) + vr <- chatVersionRange + contacts <- withStore' $ \db -> getDeletedContacts db vr user forM_ contacts $ \ct -> withStore (\db -> deleteContactWithoutGroups db user ct) `catchChatError` (toView . CRChatError (Just user)) cleanupMessages = do ts <- liftIO getCurrentTime let cutoffTs = addUTCTime (-(30 * nominalDay)) ts - withStoreCtx' (Just "cleanupManager, deleteOldMessages") (`deleteOldMessages` cutoffTs) + withStore' (`deleteOldMessages` cutoffTs) cleanupProbes = do ts <- liftIO getCurrentTime let cutoffTs = addUTCTime (-(14 * nominalDay)) ts @@ -3097,7 +3225,7 @@ deleteTimedItem user (ChatRef cType chatId, itemId) deleteAt = do vr <- chatVersionRange case cType of CTDirect -> do - (ct, CChatItem _ ci) <- withStore $ \db -> (,) <$> getContact db user chatId <*> getDirectChatItem db user chatId itemId + (ct, CChatItem _ ci) <- withStore $ \db -> (,) <$> getContact db vr user chatId <*> getDirectChatItem db user chatId itemId deleteDirectCI user ct ci True True >>= toView CTGroup -> do (gInfo, CChatItem _ ci) <- withStore $ \db -> (,) <$> getGroupInfo db vr user chatId <*> getGroupChatItem db user chatId itemId @@ -3120,11 +3248,11 @@ expireChatItems user@User {userId} ttl sync = do -- this is to keep group messages created during last 12 hours even if they're expired according to item_ts createdAtCutoff = addUTCTime (-43200 :: NominalDiffTime) currentTs waitChatStartedAndActivated - contacts <- withStoreCtx' (Just "expireChatItems, getUserContacts") (`getUserContacts` user) + contacts <- withStore' $ \db -> getUserContacts db vr user loop contacts $ processContact expirationDate waitChatStartedAndActivated - groups <- withStoreCtx' (Just "expireChatItems, getUserGroupDetails") (\db -> getUserGroupDetails db vr user Nothing Nothing) - loop groups $ processGroup expirationDate createdAtCutoff + groups <- withStore' $ \db -> getUserGroupDetails db vr user Nothing Nothing + loop groups $ processGroup vr expirationDate createdAtCutoff where loop :: [a] -> (a -> m ()) -> m () loop [] _ = pure () @@ -3142,17 +3270,19 @@ expireChatItems user@User {userId} ttl sync = do processContact :: UTCTime -> Contact -> m () processContact expirationDate ct = do waitChatStartedAndActivated - filesInfo <- withStoreCtx' (Just "processContact, getContactExpiredFileInfo") $ \db -> getContactExpiredFileInfo db user ct expirationDate - deleteFilesAndConns user filesInfo - withStoreCtx' (Just "processContact, deleteContactExpiredCIs") $ \db -> deleteContactExpiredCIs db user ct expirationDate - processGroup :: UTCTime -> UTCTime -> GroupInfo -> m () - processGroup expirationDate createdAtCutoff gInfo = do + filesInfo <- withStore' $ \db -> getContactExpiredFileInfo db user ct expirationDate + cancelFilesInProgress user filesInfo + deleteFilesLocally filesInfo + withStore' $ \db -> deleteContactExpiredCIs db user ct expirationDate + processGroup :: (PQSupport -> VersionRangeChat) -> UTCTime -> UTCTime -> GroupInfo -> m () + processGroup vr expirationDate createdAtCutoff gInfo = do waitChatStartedAndActivated - filesInfo <- withStoreCtx' (Just "processGroup, getGroupExpiredFileInfo") $ \db -> getGroupExpiredFileInfo db user gInfo expirationDate createdAtCutoff - deleteFilesAndConns user filesInfo - withStoreCtx' (Just "processGroup, deleteGroupExpiredCIs") $ \db -> deleteGroupExpiredCIs db user gInfo expirationDate createdAtCutoff - membersToDelete <- withStoreCtx' (Just "processGroup, getGroupMembersForExpiration") $ \db -> getGroupMembersForExpiration db user gInfo - forM_ membersToDelete $ \m -> withStoreCtx' (Just "processGroup, deleteGroupMember") $ \db -> deleteGroupMember db user m + filesInfo <- withStore' $ \db -> getGroupExpiredFileInfo db user gInfo expirationDate createdAtCutoff + cancelFilesInProgress user filesInfo + deleteFilesLocally filesInfo + withStore' $ \db -> deleteGroupExpiredCIs db user gInfo expirationDate createdAtCutoff + membersToDelete <- withStore' $ \db -> getGroupMembersForExpiration db vr user gInfo + forM_ membersToDelete $ \m -> withStore' $ \db -> deleteGroupMember db user m processAgentMessage :: forall m. ChatMonad m => ACorrId -> ConnId -> ACommand 'Agent 'AEConn -> m () processAgentMessage _ connId (DEL_RCVQ srv qId err_) = @@ -3161,10 +3291,24 @@ processAgentMessage _ connId DEL_CONN = toView $ CRAgentConnDeleted (AgentConnId connId) processAgentMessage corrId connId msg = do vr <- chatVersionRange - withStore' (`getUserByAConnId` AgentConnId connId) >>= \case + -- getUserByAConnId never throws logical errors, only SEDBBusyError can be thrown here + critical (withStore' (`getUserByAConnId` AgentConnId connId)) >>= \case Just user -> processAgentMessageConn vr user corrId connId msg `catchChatError` (toView . CRChatError (Just user)) _ -> throwChatError $ CENoConnectionUser (AgentConnId connId) +-- CRITICAL error will be shown to the user as alert with restart button in Android/desktop apps. +-- SEDBBusyError will only be thrown on IO exceptions or SQLError during DB queries, +-- e.g. when database is locked or busy for longer than 3s. +-- In this case there is no better mitigation than showing alert: +-- - without ACK the message delivery will be stuck, +-- - with ACK message will be lost, as it failed to be saved. +-- Full app restart is likely to resolve database condition and the message will be received and processed again. +critical :: ChatMonad m => m a -> m a +critical a = + a `catchChatError` \case + ChatErrorStore SEDBBusyError {message} -> throwError $ ChatErrorAgent (CRITICAL True message) Nothing + e -> throwError e + processAgentMessageNoConn :: forall m. ChatMonad m => ACommand 'Agent 'AENone -> m () processAgentMessageNoConn = \case CONNECT p h -> hostEvent $ CRHostConnected p h @@ -3196,7 +3340,7 @@ processAgentMsgSndFile _corrId aFileId msg = where process :: User -> m () process user = do - (ft@FileTransferMeta {fileId, cancelled}, sfts) <- withStore $ \db -> do + (ft@FileTransferMeta {fileId, xftpRedirectFor, cancelled}, sfts) <- withStore $ \db -> do fileId <- getXFTPSndFileDBId db user $ AgentSndFileId aFileId getSndFileTransfer db user fileId vr <- chatVersionRange @@ -3205,58 +3349,70 @@ processAgentMsgSndFile _corrId aFileId msg = let status = CIFSSndTransfer {sndProgress, sndTotal} ci <- withStore $ \db -> do liftIO $ updateCIFileStatus db user fileId status - getChatItemByFileId db vr user fileId + lookupChatItemByFileId db vr user fileId toView $ CRSndFileProgressXFTP user ci ft sndProgress sndTotal SFDONE sndDescr rfds -> do withStore' $ \db -> setSndFTPrivateSndDescr db user fileId (fileDescrText sndDescr) - ci@(AChatItem _ d cInfo _ci@ChatItem {meta = CIMeta {itemSharedMsgId = msgId_, itemDeleted}}) <- - withStore $ \db -> getChatItemByFileId db vr user fileId - case (msgId_, itemDeleted) of - (Just sharedMsgId, Nothing) -> do - when (length rfds < length sfts) $ throwChatError $ CEInternalError "not enough XFTP file descriptions to send" - -- TODO either update database status or move to SFPROG - toView $ CRSndFileProgressXFTP user ci ft 1 1 - case (rfds, sfts, d, cInfo) of - (rfd : extraRFDs, sft : _, SMDSnd, DirectChat ct) -> do - withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText extraRFDs) - msgDeliveryId <- sendFileDescription sft rfd sharedMsgId $ sendDirectContactMessage ct - withStore' $ \db -> updateSndFTDeliveryXFTP db sft msgDeliveryId - withAgent (`xftpDeleteSndFileInternal` aFileId) - (_, _, SMDSnd, GroupChat g@GroupInfo {groupId}) -> do - ms <- withStore' $ \db -> getGroupMembers db user g - let rfdsMemberFTs = zip rfds $ memberFTs ms - extraRFDs = drop (length rfdsMemberFTs) rfds - withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText extraRFDs) - forM_ rfdsMemberFTs $ \mt -> sendToMember mt `catchChatError` (toView . CRChatError (Just user)) - ci' <- withStore $ \db -> do - liftIO $ updateCIFileStatus db user fileId CIFSSndComplete - getChatItemByFileId db vr user fileId - withAgent (`xftpDeleteSndFileInternal` aFileId) - toView $ CRSndFileCompleteXFTP user ci' ft - where - memberFTs :: [GroupMember] -> [(Connection, SndFileTransfer)] - memberFTs ms = M.elems $ M.intersectionWith (,) (M.fromList mConns') (M.fromList sfts') + ci <- withStore $ \db -> lookupChatItemByFileId db vr user fileId + case ci of + Nothing -> do + withAgent (`xftpDeleteSndFileInternal` aFileId) + withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText rfds) + case rfds of + [] -> sendFileError "no receiver descriptions" fileId vr ft + rfd : _ -> case [fd | fd@(FD.ValidFileDescription FD.FileDescription {chunks = [_]}) <- rfds] of + [] -> case xftpRedirectFor of + Nothing -> xftpSndFileRedirect user fileId rfd >>= toView . CRSndFileRedirectStartXFTP user ft + Just _ -> sendFileError "Prohibit chaining redirects" fileId vr ft + rfds' -> do + -- we have 1 chunk - use it as URI whether it is redirect or not + ft' <- maybe (pure ft) (\fId -> withStore $ \db -> getFileTransferMeta db user fId) xftpRedirectFor + toView $ CRSndStandaloneFileComplete user ft' $ map (decodeLatin1 . strEncode . FD.fileDescriptionURI) rfds' + Just (AChatItem _ d cInfo _ci@ChatItem {meta = CIMeta {itemSharedMsgId = msgId_, itemDeleted}}) -> + case (msgId_, itemDeleted) of + (Just sharedMsgId, Nothing) -> do + when (length rfds < length sfts) $ throwChatError $ CEInternalError "not enough XFTP file descriptions to send" + -- TODO either update database status or move to SFPROG + toView $ CRSndFileProgressXFTP user ci ft 1 1 + case (rfds, sfts, d, cInfo) of + (rfd : extraRFDs, sft : _, SMDSnd, DirectChat ct) -> do + withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText extraRFDs) + msgDeliveryId <- sendFileDescription sft rfd sharedMsgId $ sendDirectContactMessage user ct + withStore' $ \db -> updateSndFTDeliveryXFTP db sft msgDeliveryId + withAgent (`xftpDeleteSndFileInternal` aFileId) + (_, _, SMDSnd, GroupChat g@GroupInfo {groupId}) -> do + ms <- withStore' $ \db -> getGroupMembers db vr user g + let rfdsMemberFTs = zip rfds $ memberFTs ms + extraRFDs = drop (length rfdsMemberFTs) rfds + withStore' $ \db -> createExtraSndFTDescrs db user fileId (map fileDescrText extraRFDs) + forM_ rfdsMemberFTs $ \mt -> sendToMember mt `catchChatError` (toView . CRChatError (Just user)) + ci' <- withStore $ \db -> do + liftIO $ updateCIFileStatus db user fileId CIFSSndComplete + getChatItemByFileId db vr user fileId + withAgent (`xftpDeleteSndFileInternal` aFileId) + toView $ CRSndFileCompleteXFTP user ci' ft where - mConns' = mapMaybe useMember ms - sfts' = mapMaybe (\sft@SndFileTransfer {groupMemberId} -> (,sft) <$> groupMemberId) sfts - useMember GroupMember {groupMemberId, activeConn = Just conn@Connection {connStatus}} - | (connStatus == ConnReady || connStatus == ConnSndReady) && not (connDisabled conn) = Just (groupMemberId, conn) - | otherwise = Nothing - useMember _ = Nothing - sendToMember :: (ValidFileDescription 'FRecipient, (Connection, SndFileTransfer)) -> m () - sendToMember (rfd, (conn, sft)) = - void $ sendFileDescription sft rfd sharedMsgId $ \msg' -> sendDirectMessage conn msg' $ GroupId groupId - _ -> pure () - _ -> pure () -- TODO error? + memberFTs :: [GroupMember] -> [(Connection, SndFileTransfer)] + memberFTs ms = M.elems $ M.intersectionWith (,) (M.fromList mConns') (M.fromList sfts') + where + mConns' = mapMaybe useMember ms + sfts' = mapMaybe (\sft@SndFileTransfer {groupMemberId} -> (,sft) <$> groupMemberId) sfts + useMember GroupMember {groupMemberId, activeConn = Just conn@Connection {connStatus}} + | (connStatus == ConnReady || connStatus == ConnSndReady) && not (connDisabled conn) = Just (groupMemberId, conn) + | otherwise = Nothing + useMember _ = Nothing + sendToMember :: (ValidFileDescription 'FRecipient, (Connection, SndFileTransfer)) -> m () + sendToMember (rfd, (conn, sft)) = + void $ sendFileDescription sft rfd sharedMsgId $ \msg' -> do + (sndMsg, msgDeliveryId, _) <- sendDirectMemberMessage conn msg' groupId + pure (sndMsg, msgDeliveryId) + _ -> pure () + _ -> pure () -- TODO error? SFERR e | temporaryAgentError e -> throwChatError $ CEXFTPSndFile fileId (AgentSndFileId aFileId) e - | otherwise -> do - ci <- withStore $ \db -> do - liftIO $ updateFileCancelled db user fileId CIFSSndError - getChatItemByFileId db vr user fileId - withAgent (`xftpDeleteSndFileInternal` aFileId) - toView $ CRSndFileError user ci + | otherwise -> + sendFileError (tshow e) fileId vr ft where fileDescrText :: FilePartyI p => ValidFileDescription p -> T.Text fileDescrText = safeDecodeUtf8 . strEncode @@ -3274,6 +3430,14 @@ processAgentMsgSndFile _corrId aFileId msg = case L.nonEmpty fds of Just fds' -> loopSend fds' Nothing -> pure msgDeliveryId + sendFileError :: Text -> Int64 -> (PQSupport -> VersionRangeChat) -> FileTransferMeta -> m () + sendFileError err fileId vr ft = do + logError $ "Sent file error: " <> err + ci <- withStore $ \db -> do + liftIO $ updateFileCancelled db user fileId CIFSSndError + lookupChatItemByFileId db vr user fileId + withAgent (`xftpDeleteSndFileInternal` aFileId) + toView $ CRSndFileError user ci ft err splitFileDescr :: ChatMonad m => RcvFileDescrText -> m (NonEmpty FileDescr) splitFileDescr rfdText = do @@ -3307,34 +3471,38 @@ processAgentMsgRcvFile _corrId aFileId msg = let status = CIFSRcvTransfer {rcvProgress, rcvTotal} ci <- withStore $ \db -> do liftIO $ updateCIFileStatus db user fileId status - getChatItemByFileId db vr user fileId - toView $ CRRcvFileProgressXFTP user ci rcvProgress rcvTotal + lookupChatItemByFileId db vr user fileId + toView $ CRRcvFileProgressXFTP user ci rcvProgress rcvTotal ft RFDONE xftpPath -> case liveRcvFileTransferPath ft of Nothing -> throwChatError $ CEInternalError "no target path for received XFTP file" Just targetPath -> do fsTargetPath <- toFSFilePath targetPath renameFile xftpPath fsTargetPath - ci <- withStore $ \db -> do + ci_ <- withStore $ \db -> do liftIO $ do updateRcvFileStatus db fileId FSComplete updateCIFileStatus db user fileId CIFSRcvComplete - getChatItemByFileId db vr user fileId + lookupChatItemByFileId db vr user fileId agentXFTPDeleteRcvFile aFileId fileId - toView $ CRRcvFileComplete user ci + toView $ maybe (CRRcvStandaloneFileComplete user fsTargetPath ft) (CRRcvFileComplete user) ci_ RFERR e | temporaryAgentError e -> throwChatError $ CEXFTPRcvFile fileId (AgentRcvFileId aFileId) e | otherwise -> do ci <- withStore $ \db -> do liftIO $ updateFileCancelled db user fileId CIFSRcvError - getChatItemByFileId db vr user fileId + lookupChatItemByFileId db vr user fileId agentXFTPDeleteRcvFile aFileId fileId - toView $ CRRcvFileError user ci e + toView $ CRRcvFileError user ci e ft -processAgentMessageConn :: forall m. ChatMonad m => VersionRange -> User -> ACorrId -> ConnId -> ACommand 'Agent 'AEConn -> m () +processAgentMessageConn :: forall m. ChatMonad m => (PQSupport -> VersionRangeChat) -> User -> ACorrId -> ConnId -> ACommand 'Agent 'AEConn -> m () processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = do - entity <- withStore (\db -> getConnectionEntity db vr user $ AgentConnId agentConnId) >>= updateConnStatus + -- Missing connection/entity errors here will be sent to the view but not shown as CRITICAL alert, + -- as in this case no need to ACK message - we can't process messages for this connection anyway. + -- SEDBException will be re-trown as CRITICAL as it is likely to indicate a temporary database condition + -- that will be resolved with app restart. + entity <- critical $ withStore (\db -> getConnectionEntity db vr user $ AgentConnId agentConnId) >>= updateConnStatus case agentMessage of END -> case entity of RcvDirectMsgConnection _ (Just ct) -> toView $ CRContactAnotherClient user ct @@ -3363,29 +3531,45 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = agentMsgConnStatus :: ACommand 'Agent e -> Maybe ConnStatus agentMsgConnStatus = \case CONF {} -> Just ConnRequested - INFO _ -> Just ConnSndReady - CON -> Just ConnReady + INFO {} -> Just ConnSndReady + CON _ -> Just ConnReady _ -> Nothing + processCONFpqSupport :: Connection -> PQSupport -> m Connection + processCONFpqSupport conn@Connection {connId, pqSupport = pq} pq' + | pq == PQSupportOn && pq' == PQSupportOff = do + let pqEnc' = CR.pqSupportToEnc pq' + withStore' $ \db -> updateConnSupportPQ db connId pq' pqEnc' + pure (conn {pqSupport = pq', pqEncryption = pqEnc'} :: Connection) + | pq /= pq' = do + messageWarning "processCONFpqSupport: unexpected pqSupport change" + pure conn + | otherwise = pure conn + + processINFOpqSupport :: Connection -> PQSupport -> m () + processINFOpqSupport Connection {pqSupport = pq} pq' = + when (pq /= pq') $ messageWarning "processINFOpqSupport: unexpected pqSupport change" + processDirectMessage :: ACommand 'Agent e -> ConnectionEntity -> Connection -> Maybe Contact -> m () - processDirectMessage agentMsg connEntity conn@Connection {connId, peerChatVRange, viaUserContactLink, customUserProfileId, connectionCode} = \case + processDirectMessage agentMsg connEntity conn@Connection {connId, connChatVersion, peerChatVRange, viaUserContactLink, customUserProfileId, connectionCode} = \case Nothing -> case agentMsg of - CONF confId _ connInfo -> do + CONF confId pqSupport _ connInfo -> do + conn' <- processCONFpqSupport conn pqSupport -- [incognito] send saved profile incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId) let profileToSend = userProfileToSend user (fromLocalProfile <$> incognitoProfile) Nothing False - conn' <- saveConnInfo conn connInfo + conn'' <- saveConnInfo conn' connInfo -- [async agent commands] no continuation needed, but command should be asynchronous for stability - allowAgentConnectionAsync user conn' confId $ XInfo profileToSend - INFO connInfo -> do + allowAgentConnectionAsync user conn'' confId $ XInfo profileToSend + INFO pqSupport connInfo -> do + processINFOpqSupport conn pqSupport _conn' <- saveConnInfo conn connInfo pure () - MSG meta _msgFlags msgBody -> do - cmdId <- createAckCmd conn + MSG meta _msgFlags msgBody -> -- TODO only acknowledge without saving message? -- probably this branch is never executed, so there should be no reason -- to save message if contact hasn't been created yet - chat item isn't created anyway - withAckMessage agentConnId cmdId meta $ do + withAckMessage agentConnId conn meta False $ \cmdId -> do (_conn', _) <- saveDirectRcvMSG conn meta cmdId msgBody pure False SENT msgId -> @@ -3417,61 +3601,64 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = forM_ contData $ \(hostConnId, xGrpMemIntroCont) -> sendXGrpMemInv hostConnId (Just directConnReq) xGrpMemIntroCont CRContactUri _ -> throwChatError $ CECommandError "unexpected ConnectionRequestUri type" - MSG msgMeta _msgFlags msgBody -> do - checkIntegrityCreateItem (CDDirectRcv ct) msgMeta - cmdId <- createAckCmd conn - withAckMessage agentConnId cmdId msgMeta $ do - (conn', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveDirectRcvMSG conn msgMeta cmdId msgBody - let ct' = ct {activeConn = Just conn'} :: Contact - assertDirectAllowed user MDRcv ct' $ toCMEventTag event - updateChatLock "directMessage" event + MSG msgMeta _msgFlags msgBody -> + withAckMessage agentConnId conn msgMeta True $ \cmdId -> do + let MsgMeta {pqEncryption} = msgMeta + (ct', conn') <- updateContactPQRcv user ct conn pqEncryption + checkIntegrityCreateItem (CDDirectRcv ct') msgMeta `catchChatError` \_ -> pure () + (conn'', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveDirectRcvMSG conn' msgMeta cmdId msgBody + let ct'' = ct' {activeConn = Just conn''} :: Contact + assertDirectAllowed user MDRcv ct'' $ toCMEventTag event + updateChatLock "direct message" event case event of - XMsgNew mc -> newContentMessage ct' mc msg msgMeta - XMsgFileDescr sharedMsgId fileDescr -> messageFileDescription ct' sharedMsgId fileDescr - XMsgUpdate sharedMsgId mContent ttl live -> messageUpdate ct' sharedMsgId mContent msg msgMeta ttl live - XMsgDel sharedMsgId _ -> messageDelete ct' sharedMsgId msg msgMeta - XMsgReact sharedMsgId _ reaction add -> directMsgReaction ct' sharedMsgId reaction add msg msgMeta + XMsgNew mc -> newContentMessage ct'' mc msg msgMeta + XMsgFileDescr sharedMsgId fileDescr -> messageFileDescription ct'' sharedMsgId fileDescr + XMsgUpdate sharedMsgId mContent ttl live -> messageUpdate ct'' sharedMsgId mContent msg msgMeta ttl live + XMsgDel sharedMsgId _ -> messageDelete ct'' sharedMsgId msg msgMeta + XMsgReact sharedMsgId _ reaction add -> directMsgReaction ct'' sharedMsgId reaction add msg msgMeta -- TODO discontinue XFile - XFile fInv -> processFileInvitation' ct' fInv msg msgMeta - XFileCancel sharedMsgId -> xFileCancel ct' sharedMsgId - XFileAcptInv sharedMsgId fileConnReq_ fName -> xFileAcptInv ct' sharedMsgId fileConnReq_ fName - XInfo p -> xInfo ct' p - XDirectDel -> xDirectDel ct' msg msgMeta - XGrpInv gInv -> processGroupInvitation ct' gInv msg msgMeta - XInfoProbe probe -> xInfoProbe (COMContact ct') probe - XInfoProbeCheck probeHash -> xInfoProbeCheck (COMContact ct') probeHash - XInfoProbeOk probe -> xInfoProbeOk (COMContact ct') probe - XCallInv callId invitation -> xCallInv ct' callId invitation msg msgMeta - XCallOffer callId offer -> xCallOffer ct' callId offer msg - XCallAnswer callId answer -> xCallAnswer ct' callId answer msg - XCallExtra callId extraInfo -> xCallExtra ct' callId extraInfo msg - XCallEnd callId -> xCallEnd ct' callId msg - BFileChunk sharedMsgId chunk -> bFileChunk ct' sharedMsgId chunk msgMeta + XFile fInv -> processFileInvitation' ct'' fInv msg msgMeta + XFileCancel sharedMsgId -> xFileCancel ct'' sharedMsgId + XFileAcptInv sharedMsgId fileConnReq_ fName -> xFileAcptInv ct'' sharedMsgId fileConnReq_ fName + XInfo p -> xInfo ct'' p + XDirectDel -> xDirectDel ct'' msg msgMeta + XGrpInv gInv -> processGroupInvitation ct'' gInv msg msgMeta + XInfoProbe probe -> xInfoProbe (COMContact ct'') probe + XInfoProbeCheck probeHash -> xInfoProbeCheck (COMContact ct'') probeHash + XInfoProbeOk probe -> xInfoProbeOk (COMContact ct'') probe + XCallInv callId invitation -> xCallInv ct'' callId invitation msg msgMeta + XCallOffer callId offer -> xCallOffer ct'' callId offer msg + XCallAnswer callId answer -> xCallAnswer ct'' callId answer msg + XCallExtra callId extraInfo -> xCallExtra ct'' callId extraInfo msg + XCallEnd callId -> xCallEnd ct'' callId msg + BFileChunk sharedMsgId chunk -> bFileChunk ct'' sharedMsgId chunk msgMeta _ -> messageError $ "unsupported message: " <> T.pack (show event) - let Contact {chatSettings = ChatSettings {sendRcpts}} = ct' + let Contact {chatSettings = ChatSettings {sendRcpts}} = ct'' pure $ fromMaybe (sendRcptsContacts user) sendRcpts && hasDeliveryReceipt (toCMEventTag event) RCVD msgMeta msgRcpt -> withAckMessage' agentConnId conn msgMeta $ directMsgReceived ct conn msgMeta msgRcpt - CONF confId _ connInfo -> do - ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo - conn' <- updatePeerChatVRange conn chatVRange + CONF confId pqSupport _ connInfo -> do + conn' <- processCONFpqSupport conn pqSupport + ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn' connInfo + conn'' <- updatePeerChatVRange conn' chatVRange case chatMsgEvent of -- confirming direct connection with a member XGrpMemInfo _memId _memProfile -> do -- TODO check member ID -- TODO update member profile -- [async agent commands] no continuation needed, but command should be asynchronous for stability - allowAgentConnectionAsync user conn' confId XOk + allowAgentConnectionAsync user conn'' confId XOk XInfo profile -> do ct' <- processContactProfileUpdate ct profile False `catchChatError` const (pure ct) -- [incognito] send incognito profile incognitoProfile <- forM customUserProfileId $ \profileId -> withStore $ \db -> getProfileById db userId profileId let p = userProfileToSend user (fromLocalProfile <$> incognitoProfile) (Just ct') False - allowAgentConnectionAsync user conn' confId $ XInfo p + allowAgentConnectionAsync user conn'' confId $ XInfo p void $ withStore' $ \db -> resetMemberContactFields db ct' _ -> messageError "CONF for existing contact must have x.grp.mem.info or x.info" - INFO connInfo -> do + INFO pqSupport connInfo -> do + processINFOpqSupport conn pqSupport ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo _conn' <- updatePeerChatVRange conn chatVRange case chatMsgEvent of @@ -3483,33 +3670,38 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = void $ processContactProfileUpdate ct profile False XOk -> pure () _ -> messageError "INFO for existing contact must have x.grp.mem.info, x.info or x.ok" - CON -> + CON pqEnc -> withStore' (\db -> getViaGroupMember db vr user ct) >>= \case Nothing -> do + when (pqEnc == PQEncOn) $ withStore' $ \db -> updateConnPQEnabledCON db connId pqEnc + let conn' = conn {pqSndEnabled = Just pqEnc, pqRcvEnabled = Just pqEnc} :: Connection + ct' = ct {activeConn = Just conn'} :: Contact -- [incognito] print incognito profile used for this contact incognitoProfile <- forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId) - setContactNetworkStatus ct NSConnected - toView $ CRContactConnected user ct (fmap fromLocalProfile incognitoProfile) - when (directOrUsed ct) $ createFeatureEnabledItems ct - when (contactConnInitiated conn) $ do - let Connection {groupLinkId} = conn + setContactNetworkStatus ct' NSConnected + toView $ CRContactConnected user ct' (fmap fromLocalProfile incognitoProfile) + when (directOrUsed ct') $ do + createInternalChatItem user (CDDirectRcv ct') (CIRcvDirectE2EEInfo $ E2EInfo pqEnc) Nothing + createFeatureEnabledItems ct' + when (contactConnInitiated conn') $ do + let Connection {groupLinkId} = conn' doProbeContacts = isJust groupLinkId - probeMatchingContactsAndMembers ct (contactConnIncognito ct) doProbeContacts - withStore' $ \db -> resetContactConnInitiated db user conn + probeMatchingContactsAndMembers ct' (contactConnIncognito ct') doProbeContacts + withStore' $ \db -> resetContactConnInitiated db user conn' forM_ viaUserContactLink $ \userContactLinkId -> do ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId let (UserContactLink {autoAccept}, groupId_, gLinkMemRole) = ucl forM_ autoAccept $ \(AutoAccept {autoReply = mc_}) -> forM_ mc_ $ \mc -> do - (msg, _) <- sendDirectContactMessage ct (XMsgNew $ MCSimple (extMsgContent mc Nothing)) - ci <- saveSndChatItem user (CDDirectSnd ct) msg (CISndMsgContent mc) - toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct) ci) + (msg, _) <- sendDirectContactMessage user ct' (XMsgNew $ MCSimple (extMsgContent mc Nothing)) + ci <- saveSndChatItem user (CDDirectSnd ct') msg (CISndMsgContent mc) + toView $ CRNewChatItem user (AChatItem SCTDirect SMDSnd (DirectChat ct') ci) forM_ groupId_ $ \groupId -> do groupInfo <- withStore $ \db -> getGroupInfo db vr user groupId subMode <- chatReadVar subscriptionMode groupConnIds <- createAgentConnectionAsync user CFCreateConnGrpInv True SCMInvitation subMode gVar <- asks random - withStore $ \db -> createNewContactMemberAsync db gVar user groupInfo ct gLinkMemRole groupConnIds (fromJVersionRange peerChatVRange) subMode + withStore $ \db -> createNewContactMemberAsync db gVar user groupInfo ct' gLinkMemRole groupConnIds connChatVersion peerChatVRange subMode Just (gInfo, m@GroupMember {activeConn}) -> when (maybe False ((== ConnReady) . connStatus) activeConn) $ do notifyMemberConnected gInfo m $ Just ct @@ -3538,18 +3730,15 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = processErr cryptoErr = do let e@(mde, n) = agentMsgDecryptError cryptoErr ci_ <- withStore $ \db -> - getDirectChatItemsLast db user contactId 1 "" + getDirectChatItemLast db user contactId >>= liftIO . mapM (\(ci, content') -> updateDirectChatItem' db user contactId ci content' False Nothing) - . (mdeUpdatedCI e <=< headMaybe) + . mdeUpdatedCI e case ci_ of Just ci -> toView $ CRChatItemUpdated user (AChatItem SCTDirect SMDRcv (DirectChat ct) ci) _ -> do toView $ CRContactRatchetSync user ct (RatchetSyncProgress rss cStats) createInternalChatItem user (CDDirectRcv ct) (CIRcvDecryptionError mde n) Nothing - headMaybe = \case - x : _ -> Just x - _ -> Nothing ratchetSyncEventItem ct' = do toView $ CRContactRatchetSync user ct' (RatchetSyncProgress rss cStats) createInternalChatItem user (CDDirectRcv ct') (CIRcvConnEvent $ RCERatchetSync rss) Nothing @@ -3579,7 +3768,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = groupConnReq@(CRInvitationUri _ _) -> case cmdFunction of -- [async agent commands] XGrpMemIntro continuation on receiving INV CFCreateConnGrpMemInv - | isCompatibleRange (fromJVersionRange $ peerChatVRange conn) groupNoDirectVRange -> sendWithoutDirectCReq + | maxVersion (peerChatVRange conn) >= groupDirectInvVersion -> sendWithoutDirectCReq | otherwise -> sendWithDirectCReq where sendWithoutDirectCReq = do @@ -3597,7 +3786,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = sendXGrpMemInv hostConnId (Just directConnReq) XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq} -- [async agent commands] group link auto-accept continuation on receiving INV CFCreateConnGrpInv -> do - ct <- withStore $ \db -> getContactViaMember db user m + ct <- withStore $ \db -> getContactViaMember db vr user m withStore' $ \db -> setNewContactMemberConnRequest db user m cReq groupLinkId <- withStore' $ \db -> getGroupLinkId db user gInfo sendGrpInvitation ct m groupLinkId @@ -3605,14 +3794,23 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = where sendGrpInvitation :: Contact -> GroupMember -> Maybe GroupLinkId -> m () sendGrpInvitation ct GroupMember {memberId, memberRole = memRole} groupLinkId = do + currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo let GroupMember {memberRole = userRole, memberId = userMemberId} = membership - groupInv = GroupInvitation (MemberIdRole userMemberId userRole) (MemberIdRole memberId memRole) cReq groupProfile groupLinkId - (_msg, _) <- sendDirectContactMessage ct $ XGrpInv groupInv + groupInv = + GroupInvitation + { fromMember = MemberIdRole userMemberId userRole, + invitedMember = MemberIdRole memberId memRole, + connRequest = cReq, + groupProfile, + groupLinkId = groupLinkId, + groupSize = Just currentMemCount + } + (_msg, _) <- sendDirectContactMessage user ct $ XGrpInv groupInv -- we could link chat item with sent group invitation message (_msg) createInternalChatItem user (CDGroupRcv gInfo m) (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing _ -> throwChatError $ CECommandError "unexpected cmdFunction" CRContactUri _ -> throwChatError $ CECommandError "unexpected ConnectionRequestUri type" - CONF confId _ connInfo -> do + CONF confId _pqSupport _ connInfo -> do ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo conn' <- updatePeerChatVRange conn chatVRange case memberCategory m of @@ -3636,7 +3834,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = allowAgentConnectionAsync user conn' confId $ XGrpMemInfo membershipMemId membershipProfile | otherwise -> messageError "x.grp.mem.info: memberId is different from expected" _ -> messageError "CONF from member must have x.grp.mem.info" - INFO connInfo -> do + INFO _pqSupport connInfo -> do ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo _conn' <- updatePeerChatVRange conn chatVRange case chatMsgEvent of @@ -3649,7 +3847,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = XOk -> pure () _ -> messageError "INFO from member must have x.grp.mem.info, x.info or x.ok" pure () - CON -> do + CON _pqEnc -> do withStore' $ \db -> do updateGroupMemberStatus db userId m GSMemConnected unless (memberActive membership) $ @@ -3660,20 +3858,19 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = case memberCategory m of GCHostMember -> do toView $ CRUserJoinedGroup user gInfo {membership = membership {memberStatus = GSMemConnected}} m {memberStatus = GSMemConnected} + createInternalChatItem user (CDGroupRcv gInfo m) (CIRcvGroupE2EEInfo $ E2EInfo {pqEnabled = PQEncOff}) Nothing createGroupFeatureItems gInfo m let GroupInfo {groupProfile = GroupProfile {description}} = gInfo memberConnectedChatItem gInfo m unless expectHistory $ forM_ description $ groupDescriptionChatItem gInfo m where - expectHistory = - groupFeatureAllowed SGFHistory gInfo - && isCompatibleRange (memberChatVRange' m) groupHistoryIncludeWelcomeVRange + expectHistory = groupFeatureAllowed SGFHistory gInfo && m `supportsVersion` groupHistoryIncludeWelcomeVersion GCInviteeMember -> do memberConnectedChatItem gInfo m toView $ CRJoinedGroupMember user gInfo m {memberStatus = GSMemConnected} let Connection {viaUserContactLink} = conn when (isJust viaUserContactLink && isNothing (memberContactId m)) sendXGrpLinkMem - members <- withStore' $ \db -> getGroupMembers db user gInfo + members <- withStore' $ \db -> getGroupMembers db vr user gInfo void . sendGroupMessage user gInfo members . XGrpMemNew $ memberInfo m sendIntroductions members when (groupFeatureAllowed SGFHistory gInfo) sendHistory @@ -3681,11 +3878,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = sendXGrpLinkMem = do let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo profileToSend = profileToSendOnAccept user profileMode True - void $ sendDirectMessage conn (XGrpLinkMem profileToSend) (GroupId groupId) + void $ sendDirectMemberMessage conn (XGrpLinkMem profileToSend) groupId sendIntroductions members = do - intros <- withStore' $ \db -> createIntroductions db (maxVersion vr) members m + intros <- withStore' $ \db -> createIntroductions db (maxVersion $ vr PQSupportOff) members m shuffledIntros <- liftIO $ shuffleIntros intros - if isCompatibleRange (memberChatVRange' m) batchSendVRange + if m `supportsVersion` batchSendVersion then do let events = map (memberIntro . reMember) shuffledIntros forM_ (L.nonEmpty events) $ \events' -> @@ -3707,10 +3904,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = isAdmin GroupMemberIntro {reMember = GroupMember {memberRole}} = memberRole >= GRAdmin hasPicture GroupMemberIntro {reMember = GroupMember {memberProfile = LocalProfile {image}}} = isJust image processIntro intro@GroupMemberIntro {introId} = do - void $ sendDirectMessage conn (memberIntro $ reMember intro) (GroupId groupId) + void $ sendDirectMemberMessage conn (memberIntro $ reMember intro) groupId withStore' $ \db -> updateIntroStatus db introId GMIntroSent sendHistory = - when (isCompatibleRange (memberChatVRange' m) batchSendVRange) $ do + when (m `supportsVersion` batchSendVersion) $ do (errs, items) <- partitionEithers <$> withStore' (\db -> getGroupHistoryItems db user gInfo 100) (errs', events) <- partitionEithers <$> mapM (tryChatError . itemForwardEvents) items let errors = map ChatErrorStore errs <> errs' @@ -3720,7 +3917,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = sendGroupMemberMessages user conn events'' groupId descrEvent_ :: Maybe (ChatMsgEvent 'Json) descrEvent_ - | isCompatibleRange (memberChatVRange' m) groupHistoryIncludeWelcomeVRange = do + | m `supportsVersion` groupHistoryIncludeWelcomeVersion = do let GroupInfo {groupProfile = GroupProfile {description}} = gInfo fmap (\descr -> XMsgNew $ MCSimple $ extMsgContent (MCText descr) Nothing) description | otherwise = Nothing @@ -3787,7 +3984,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = pure msgForwardEvents _ -> do let memCategory = memberCategory m - withStore' (\db -> getViaGroupContact db user m) >>= \case + withStore' (\db -> getViaGroupContact db vr user m) >>= \case Nothing -> do notifyMemberConnected gInfo m Nothing let connectedIncognito = memberIncognito membership @@ -3804,20 +4001,18 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = sendXGrpMemCon = \case GCPreMember -> forM_ (invitedByGroupMemberId membership) $ \hostId -> do - host <- withStore $ \db -> getGroupMember db user groupId hostId + host <- withStore $ \db -> getGroupMember db vr user groupId hostId forM_ (memberConn host) $ \hostConn -> - void $ sendDirectMessage hostConn (XGrpMemCon memberId) (GroupId groupId) + void $ sendDirectMemberMessage hostConn (XGrpMemCon memberId) groupId GCPostMember -> forM_ (invitedByGroupMemberId m) $ \invitingMemberId -> do - im <- withStore $ \db -> getGroupMember db user groupId invitingMemberId + im <- withStore $ \db -> getGroupMember db vr user groupId invitingMemberId forM_ (memberConn im) $ \imConn -> - void $ sendDirectMessage imConn (XGrpMemCon memberId) (GroupId groupId) + void $ sendDirectMemberMessage imConn (XGrpMemCon memberId) groupId _ -> messageWarning "sendXGrpMemCon: member category GCPreMember or GCPostMember is expected" MSG msgMeta _msgFlags msgBody -> do - checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta - cmdId <- createAckCmd conn - let aChatMsgs = parseChatMessages msgBody - withAckMessage agentConnId cmdId msgMeta $ do + withAckMessage agentConnId conn msgMeta True $ \cmdId -> do + checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta `catchChatError` \_ -> pure () forM_ aChatMsgs $ \case Right (ACMsg _ chatMsg) -> processEvent cmdId chatMsg `catchChatError` \e -> toView $ CRChatError (Just user) e @@ -3829,6 +4024,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = [Right (ACMsg _ chatMsg)] -> forwardMsg_ chatMsg _ -> pure () where + aChatMsgs = parseChatMessages msgBody brokerTs = metaBrokerTs msgMeta processEvent :: MsgEncodingI e => CommandId -> ChatMessage e -> m () processEvent cmdId chatMsg = do @@ -3865,12 +4061,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = BFileChunk sharedMsgId chunk -> bFileChunkGroup gInfo sharedMsgId chunk msgMeta _ -> messageError $ "unsupported message: " <> T.pack (show event) checkSendRcpt :: [AChatMessage] -> m Bool - checkSendRcpt aChatMsgs = do + checkSendRcpt aMsgs = do currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo let GroupInfo {chatSettings = ChatSettings {sendRcpts}} = gInfo pure $ fromMaybe (sendRcptsSmallGroups user) sendRcpts - && any aChatMsgHasReceipt aChatMsgs + && any aChatMsgHasReceipt aMsgs && currentMemCount <= smallGroupsRcptsMemLimit where aChatMsgHasReceipt (ACMsg _ ChatMessage {chatMsgEvent}) = @@ -3882,10 +4078,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- members introduced to this invited member introducedMembers <- if memberCategory m == GCInviteeMember - then withStore' $ \db -> getForwardIntroducedMembers db user m highlyAvailable + then withStore' $ \db -> getForwardIntroducedMembers db vr user m highlyAvailable else pure [] -- invited members to which this member was introduced - invitedMembers <- withStore' $ \db -> getForwardInvitedMembers db user m highlyAvailable + invitedMembers <- withStore' $ \db -> getForwardInvitedMembers db vr user m highlyAvailable let GroupMember {memberId} = m ms = forwardedToGroupMembers (introducedMembers <> invitedMembers) chatMsg' msg = XGrpMsgForward memberId chatMsg' brokerTs @@ -3983,7 +4179,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = case agentMsg of -- SMP CONF for SndFileConnection happens for direct file protocol -- when recipient of the file "joins" connection created by the sender - CONF confId _ connInfo -> do + CONF confId _pqSupport _ connInfo -> do ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo conn' <- updatePeerChatVRange conn chatVRange case chatMsgEvent of @@ -3995,7 +4191,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = allowAgentConnectionAsync user conn' confId XOk | otherwise -> messageError "x.file.acpt: fileName is different from expected" _ -> messageError "CONF from file connection must have x.file.acpt" - CON -> do + CON _ -> do ci <- withStore $ \db -> do liftIO $ updateSndFileStatus db ft FSConnected updateDirectCIFileStatus db vr user fileId $ CIFSSndTransfer 0 1 @@ -4009,10 +4205,10 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = case err of SMP SMP.AUTH -> unless (fileStatus == FSCancelled) $ do ci <- withStore $ \db -> do - getChatRefByFileId db user fileId >>= \case - ChatRef CTDirect _ -> liftIO $ updateFileCancelled db user fileId CIFSSndCancelled + liftIO (lookupChatRefByFileId db user fileId) >>= \case + Just (ChatRef CTDirect _) -> liftIO $ updateFileCancelled db user fileId CIFSSndCancelled _ -> pure () - getChatItemByFileId db vr user fileId + lookupChatItemByFileId db vr user fileId toView $ CRSndFileRcvCancelled user ci ft _ -> throwChatError $ CEFileSend fileId err MSG meta _ _ -> withAckMessage' agentConnId conn meta $ pure () @@ -4034,17 +4230,17 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = fileInvConnReq@(CRInvitationUri _ _) -> case cmdFunction of -- [async agent commands] direct XFileAcptInv continuation on receiving INV CFCreateConnFileInvDirect -> do - ct <- withStore $ \db -> getContactByFileId db user fileId + ct <- withStore $ \db -> getContactByFileId db vr user fileId sharedMsgId <- withStore $ \db -> getSharedMsgIdByFileId db userId fileId - void $ sendDirectContactMessage ct (XFileAcptInv sharedMsgId (Just fileInvConnReq) fileName) + void $ sendDirectContactMessage user ct (XFileAcptInv sharedMsgId (Just fileInvConnReq) fileName) -- [async agent commands] group XFileAcptInv continuation on receiving INV CFCreateConnFileInvGroup -> case grpMemberId of Just gMemberId -> do - GroupMember {groupId, activeConn} <- withStore $ \db -> getGroupMemberById db user gMemberId + GroupMember {groupId, activeConn} <- withStore $ \db -> getGroupMemberById db vr user gMemberId case activeConn of Just gMemberConn -> do sharedMsgId <- withStore $ \db -> getSharedMsgIdByFileId db userId fileId - void $ sendDirectMessage gMemberConn (XFileAcptInv sharedMsgId (Just fileInvConnReq) fileName) $ GroupId groupId + void $ sendDirectMemberMessage gMemberConn (XFileAcptInv sharedMsgId (Just fileInvConnReq) fileName) groupId _ -> throwChatError $ CECommandError "no GroupMember activeConn" _ -> throwChatError $ CECommandError "no grpMemberId" _ -> throwChatError $ CECommandError "unexpected cmdFunction" @@ -4052,14 +4248,15 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- SMP CONF for RcvFileConnection happens for group file protocol -- when sender of the file "joins" connection created by the recipient -- (sender doesn't create connections for all group members) - CONF confId _ connInfo -> do + CONF confId _pqSupport _ connInfo -> do ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo conn' <- updatePeerChatVRange conn chatVRange case chatMsgEvent of XOk -> allowAgentConnectionAsync user conn' confId XOk -- [async agent commands] no continuation needed, but command should be asynchronous for stability _ -> pure () - CON -> startReceivingFile user fileId + CON _ -> startReceivingFile user fileId MSG meta _ msgBody -> do + -- XXX: not all branches do ACK parseFileChunk msgBody >>= receiveFileChunk ft (Just conn) meta OK -> -- [async agent commands] continuation on receiving OK @@ -4113,11 +4310,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = processUserContactRequest :: ACommand 'Agent e -> ConnectionEntity -> Connection -> UserContact -> m () processUserContactRequest agentMsg connEntity conn UserContact {userContactLinkId} = case agentMsg of - REQ invId _ connInfo -> do + REQ invId pqSupport _ connInfo -> do ChatMessage {chatVRange, chatMsgEvent} <- parseChatMessage conn connInfo case chatMsgEvent of - XContact p xContactId_ -> profileContactRequest invId chatVRange p xContactId_ - XInfo p -> profileContactRequest invId chatVRange p Nothing + XContact p xContactId_ -> profileContactRequest invId chatVRange p xContactId_ pqSupport + XInfo p -> profileContactRequest invId chatVRange p Nothing pqSupport -- TODO show/log error, other events in contact request _ -> pure () MERR _ err -> do @@ -4129,9 +4326,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- TODO add debugging output _ -> pure () where - profileContactRequest :: InvitationId -> VersionRange -> Profile -> Maybe XContactId -> m () - profileContactRequest invId chatVRange p xContactId_ = do - withStore (\db -> createOrUpdateContactRequest db user userContactLinkId invId chatVRange p xContactId_) >>= \case + profileContactRequest :: InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> PQSupport -> m () + profileContactRequest invId chatVRange p xContactId_ reqPQSup = do + withStore (\db -> createOrUpdateContactRequest db vr user userContactLinkId invId chatVRange p xContactId_ reqPQSup) >>= \case CORContact contact -> toView $ CRContactRequestAlreadyAccepted user contact CORRequest cReq -> do ucl <- withStore $ \db -> getUserContactLinkById db userId userContactLinkId @@ -4141,18 +4338,21 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = Nothing -> do -- [incognito] generate profile to send, create connection with incognito profile incognitoProfile <- if acceptIncognito then Just . NewIncognito <$> liftIO generateRandomProfile else pure Nothing - ct <- acceptContactRequestAsync user cReq incognitoProfile True + pqSup <- chatReadVar pqExperimentalEnabled + let pqSup' = pqSup `CR.pqSupportAnd` reqPQSup + ct <- acceptContactRequestAsync user cReq incognitoProfile True pqSup' toView $ CRAcceptingContactRequest user ct Just groupId -> do gInfo <- withStore $ \db -> getGroupInfo db vr user groupId let profileMode = ExistingIncognito <$> incognitoMembershipProfile gInfo - if isCompatibleRange chatVRange groupLinkNoContactVRange + if maxVersion chatVRange >= groupFastLinkJoinVersion then do mem <- acceptGroupJoinRequestAsync user gInfo cReq gLinkMemRole profileMode createInternalChatItem user (CDGroupRcv gInfo mem) (CIRcvGroupEvent RGEInvitedViaGroupLink) Nothing toView $ CRAcceptingGroupJoinRequestMember user gInfo mem else do - ct <- acceptContactRequestAsync user cReq profileMode False + -- TODO v5.7 remove old API (or v6.0?) + ct <- acceptContactRequestAsync user cReq profileMode False PQSupportOff toView $ CRAcceptingGroupJoinRequest user gInfo ct _ -> toView $ CRReceivedContactRequest user cReq @@ -4200,19 +4400,22 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withAckMessage' :: ConnId -> Connection -> MsgMeta -> m () -> m () withAckMessage' cId conn msgMeta action = do - cmdId <- createAckCmd conn - withAckMessage cId cmdId msgMeta $ action $> False + withAckMessage cId conn msgMeta False $ \_cmdId -> action $> False - withAckMessage :: ConnId -> CommandId -> MsgMeta -> m Bool -> m () - withAckMessage cId cmdId msgMeta action = do + withAckMessage :: ConnId -> Connection -> MsgMeta -> Bool -> (CommandId -> m Bool) -> m () + withAckMessage cId conn msgMeta showCritical action = do + cmdId <- createAckCmd conn `catchChatError` \e -> throwError $ ChatErrorAgent (CRITICAL True $ show e) Nothing -- [async agent commands] command should be asynchronous, continuation is ackMsgDeliveryEvent -- TODO catching error and sending ACK after an error, particularly if it is a database error, will result in the message not processed (and no notification to the user). -- Possible solutions are: -- 1) retry processing several times -- 2) stabilize database -- 3) show screen of death to the user asking to restart - tryChatError action >>= \case + tryChatError (action cmdId) >>= \case Right withRcpt -> ackMsg cId cmdId msgMeta $ if withRcpt then Just "" else Nothing + -- If showCritical is True, then these errors don't result in ACK and show user visible alert + -- This prevents losing the message that failed to be processed. + Left (ChatErrorStore SEDBBusyError {message}) | showCritical -> throwError $ ChatErrorAgent (CRITICAL True message) Nothing Left e -> ackMsg cId cmdId msgMeta Nothing >> throwError e ackMsg :: ConnId -> CommandId -> MsgMeta -> Maybe MsgReceiptInfo -> m () @@ -4265,14 +4468,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = sendProbe probe cs <- if doProbeContacts - then map COMContact <$> withStore' (\db -> getMatchingContacts db user ct) + then map COMContact <$> withStore' (\db -> getMatchingContacts db vr user ct) else pure [] - ms <- map COMGroupMember <$> withStore' (\db -> getMatchingMembers db user ct) + ms <- map COMGroupMember <$> withStore' (\db -> getMatchingMembers db vr user ct) sendProbeHashes (cs <> ms) probe probeId else sendProbe . Probe =<< liftIO (encodedRandomBytes gVar 32) where sendProbe :: Probe -> m () - sendProbe probe = void . sendDirectContactMessage ct $ XInfoProbe probe + sendProbe probe = void . sendDirectContactMessage user ct $ XInfoProbe probe probeMatchingMemberContact :: GroupMember -> IncognitoEnabled -> m () probeMatchingMemberContact GroupMember {activeConn = Nothing} _ = pure () @@ -4283,12 +4486,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = then do (probe, probeId) <- withStore $ \db -> createSentProbe db gVar userId $ COMGroupMember m sendProbe probe - cs <- map COMContact <$> withStore' (\db -> getMatchingMemberContacts db user m) + cs <- map COMContact <$> withStore' (\db -> getMatchingMemberContacts db vr user m) sendProbeHashes cs probe probeId else sendProbe . Probe =<< liftIO (encodedRandomBytes gVar 32) where sendProbe :: Probe -> m () - sendProbe probe = void $ sendDirectMessage conn (XInfoProbe probe) (GroupId groupId) + sendProbe probe = void $ sendDirectMemberMessage conn (XInfoProbe probe) groupId sendProbeHashes :: [ContactOrMember] -> Probe -> Int64 -> m () sendProbeHashes cgms probe probeId = @@ -4297,12 +4500,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = probeHash = ProbeHash $ C.sha256Hash (unProbe probe) sendProbeHash :: ContactOrMember -> m () sendProbeHash cgm@(COMContact c) = do - void . sendDirectContactMessage c $ XInfoProbeCheck probeHash + void . sendDirectContactMessage user c $ XInfoProbeCheck probeHash withStore' $ \db -> createSentProbeHash db userId probeId cgm sendProbeHash (COMGroupMember GroupMember {activeConn = Nothing}) = pure () sendProbeHash cgm@(COMGroupMember m@GroupMember {groupId, activeConn = Just conn}) = when (memberCurrent m) $ do - void $ sendDirectMessage conn (XInfoProbeCheck probeHash) (GroupId groupId) + void $ sendDirectMemberMessage conn (XInfoProbeCheck probeHash) groupId withStore' $ \db -> createSentProbeHash db userId probeId cgm messageWarning :: Text -> m () @@ -4489,7 +4692,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | isVoice content && not (groupFeatureAllowed SGFVoice gInfo) = rejected GFVoice | not (isVoice content) && isJust fInv_ && not (groupFeatureAllowed SGFFiles gInfo) = rejected GFFiles | otherwise = - withStore' (\db -> getCIModeration db user gInfo memberId sharedMsgId_) >>= \case + withStore' (\db -> getCIModeration db vr user gInfo memberId sharedMsgId_) >>= \case Just ciModeration -> do applyModeration ciModeration withStore' $ \db -> deleteCIModeration db gInfo memberId sharedMsgId_ @@ -4659,9 +4862,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- receiving via a separate connection Just fileConnReq -> do subMode <- chatReadVar subscriptionMode - dm <- directMessage XOk + dm <- encodeConnInfo XOk connIds <- joinAgentConnectionAsync user True fileConnReq dm subMode - withStore' $ \db -> createSndDirectFTConnection db user fileId connIds subMode + withStore' $ \db -> createSndDirectFTConnection db vr user fileId connIds subMode -- receiving inline _ -> do event <- withStore $ \db -> do @@ -4671,7 +4874,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView event ifM (allowSendInline fileSize fileInline) - (sendDirectFileInline ct ft sharedMsgId) + (sendDirectFileInline user ct ft sharedMsgId) (messageError "x.file.acpt.inv: fileSize is bigger than allowed to send inline") else messageError "x.file.acpt.inv: fileName is different from expected" @@ -4756,9 +4959,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = subMode <- chatReadVar subscriptionMode -- receiving via a separate connection -- [async agent commands] no continuation needed, but command should be asynchronous for stability - dm <- directMessage XOk + dm <- encodeConnInfo XOk connIds <- joinAgentConnectionAsync user True fileConnReq dm subMode - withStore' $ \db -> createSndGroupFileTransferConnection db user fileId connIds m subMode + withStore' $ \db -> createSndGroupFileTransferConnection db vr user fileId connIds m subMode (_, Just conn) -> do -- receiving inline event <- withStore $ \db -> do @@ -4781,7 +4984,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = processGroupInvitation ct inv msg msgMeta = do let Contact {localDisplayName = c, activeConn} = ct GroupInvitation {fromMember = (MemberIdRole fromMemId fromRole), invitedMember = (MemberIdRole memId memRole), connRequest, groupLinkId} = inv - forM_ activeConn $ \Connection {connId, peerChatVRange, customUserProfileId, groupLinkId = groupLinkId'} -> do + forM_ activeConn $ \Connection {connId, connChatVersion, peerChatVRange, customUserProfileId, groupLinkId = groupLinkId'} -> do when (fromRole < GRAdmin || fromRole < memRole) $ throwChatError (CEGroupContactRole c) when (fromMemId == memId) $ throwChatError CEGroupDuplicateMemberId -- [incognito] if direct connection with host is incognito, create membership using the same incognito profile @@ -4790,11 +4993,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = if sameGroupLinkId groupLinkId groupLinkId' then do subMode <- chatReadVar subscriptionMode - dm <- directMessage $ XGrpAcpt membershipMemId + dm <- encodeConnInfo $ XGrpAcpt membershipMemId connIds <- joinAgentConnectionAsync user True connRequest dm subMode withStore' $ \db -> do setViaGroupLinkHash db groupId connId - createMemberConnectionAsync db user hostId connIds (fromJVersionRange peerChatVRange) subMode + createMemberConnectionAsync db user hostId connIds connChatVersion peerChatVRange subMode updateGroupMemberStatusById db userId hostId GSMemAccepted updateGroupMemberStatus db userId membership GSMemAccepted toView $ CRUserAcceptedGroupSent user gInfo {membership = membership {memberStatus = GSMemAccepted}} (Just ct) @@ -4813,9 +5016,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = checkIntegrityCreateItem :: forall c. ChatTypeI c => ChatDirection c 'MDRcv -> MsgMeta -> m () checkIntegrityCreateItem cd MsgMeta {integrity, broker = (_, brokerTs)} = case integrity of MsgOk -> pure () - MsgError e -> - createInternalChatItem user cd (CIRcvIntegrityError e) (Just brokerTs) - `catchChatError` \_ -> pure () + MsgError e -> createInternalChatItem user cd (CIRcvIntegrityError e) (Just brokerTs) xInfo :: Contact -> Profile -> m () xInfo c p' = void $ processContactProfileUpdate c p' True @@ -4825,7 +5026,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = if directOrUsed c then do ct' <- withStore' $ \db -> updateContactStatus db user c CSDeleted - contactConns <- withStore' $ \db -> getContactConnections db userId ct' + contactConns <- withStore' $ \db -> getContactConnections db vr userId ct' deleteAgentConnectionsAsync user $ map aConnId contactConns forM_ contactConns $ \conn -> withStore' $ \db -> updateConnectionStatus db conn ConnDeleted activeConn' <- forM (contactConn ct') $ \conn -> pure conn {connStatus = ConnDeleted} @@ -4834,7 +5035,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView $ CRNewChatItem user (AChatItem SCTDirect SMDRcv (DirectChat ct'') ci) toView $ CRContactDeletedByContact user ct'' else do - contactConns <- withStore' $ \db -> getContactConnections db userId c + contactConns <- withStore' $ \db -> getContactConnections db vr userId c deleteAgentConnectionsAsync user $ map aConnId contactConns withStore $ \db -> deleteContact db user c where @@ -4905,7 +5106,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView $ CRGroupMemberUpdated user gInfo m m' pure m' Just mContactId -> do - mCt <- withStore $ \db -> getContact db user mContactId + mCt <- withStore $ \db -> getContact db vr user mContactId if canUpdateProfile mCt then do (m', ct') <- withStore $ \db -> updateContactMemberProfile db user m mCt p' @@ -4946,7 +5147,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = contactMerge <- readTVarIO =<< asks contactMergeEnabled -- [incognito] unless connected incognito when (contactMerge && not (contactOrMemberIncognito cgm2)) $ do - cgm1s <- withStore' $ \db -> matchReceivedProbe db user cgm2 probe + cgm1s <- withStore' $ \db -> matchReceivedProbe db vr user cgm2 probe let cgm1s' = filter (not . contactOrMemberIncognito) cgm1s probeMatches cgm1s' cgm2 where @@ -4962,7 +5163,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = contactMerge <- readTVarIO =<< asks contactMergeEnabled -- [incognito] unless connected incognito when (contactMerge && not (contactOrMemberIncognito cgm1)) $ do - cgm2Probe_ <- withStore' $ \db -> matchReceivedProbeHash db user cgm1 probeHash + cgm2Probe_ <- withStore' $ \db -> matchReceivedProbeHash db vr user cgm1 probeHash forM_ cgm2Probe_ $ \(cgm2, probe) -> unless (contactOrMemberIncognito cgm2) . void $ probeMatch cgm1 cgm2 probe @@ -4974,12 +5175,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = case cgm2 of COMContact c2@Contact {contactId = cId2, profile = p2} | cId1 /= cId2 && profilesMatch p1 p2 -> do - void . sendDirectContactMessage c1 $ XInfoProbeOk probe + void . sendDirectContactMessage user c1 $ XInfoProbeOk probe COMContact <$$> mergeContacts c1 c2 | otherwise -> messageWarning "probeMatch ignored: profiles don't match or same contact id" >> pure Nothing COMGroupMember m2@GroupMember {memberProfile = p2, memberContactId} | isNothing memberContactId && profilesMatch p1 p2 -> do - void . sendDirectContactMessage c1 $ XInfoProbeOk probe + void . sendDirectContactMessage user c1 $ XInfoProbeOk probe COMContact <$$> associateMemberAndContact c1 m2 | otherwise -> messageWarning "probeMatch ignored: profiles don't match or member already has contact" >> pure Nothing COMGroupMember GroupMember {activeConn = Nothing} -> pure Nothing @@ -4987,14 +5188,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = case cgm2 of COMContact c2@Contact {profile = p2} | memberCurrent m1 && isNothing memberContactId && profilesMatch p1 p2 -> do - void $ sendDirectMessage conn (XInfoProbeOk probe) (GroupId groupId) + void $ sendDirectMemberMessage conn (XInfoProbeOk probe) groupId COMContact <$$> associateMemberAndContact c2 m1 | otherwise -> messageWarning "probeMatch ignored: profiles don't match or member already has contact or member not current" >> pure Nothing COMGroupMember _ -> messageWarning "probeMatch ignored: members are not matched with members" >> pure Nothing xInfoProbeOk :: ContactOrMember -> Probe -> m () xInfoProbeOk cgm1 probe = do - cgm2 <- withStore' $ \db -> matchSentProbe db user cgm1 probe + cgm2 <- withStore' $ \db -> matchSentProbe db vr user cgm1 probe case cgm1 of COMContact c1@Contact {contactId = cId1} -> case cgm2 of @@ -5130,7 +5331,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = _ -> pure Nothing where merge c1' c2' = do - c2'' <- withStore $ \db -> mergeContactRecords db user c1' c2' + c2'' <- withStore $ \db -> mergeContactRecords db vr user c1' c2' toView $ CRContactsMerged user c1' c2' c2'' when (directOrUsed c2'') $ showSecurityCodeChanged c2'' pure $ Just c2'' @@ -5175,7 +5376,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = associateContactWithMember :: GroupMember -> Contact -> m Contact associateContactWithMember m1@GroupMember {groupId} c2 = do - c2' <- withStore $ \db -> associateContactWithMemberRecord db user m1 c2 + c2' <- withStore $ \db -> associateContactWithMemberRecord db vr user m1 c2 g <- withStore $ \db -> getGroupInfo db vr user groupId toView $ CRContactAndMemberAssociated user c2 g m1 c2' pure c2' @@ -5201,9 +5402,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = xGrpMemNew gInfo m memInfo@(MemberInfo memId memRole _ _) msg brokerTs = do checkHostRole m memRole unless (sameMemberId memId $ membership gInfo) $ - withStore' (\db -> runExceptT $ getGroupMemberByMemberId db user gInfo memId) >>= \case + withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case Right unknownMember@GroupMember {memberStatus = GSMemUnknown} -> do - updatedMember <- withStore $ \db -> updateUnknownMemberAnnounced db user m unknownMember memInfo + updatedMember <- withStore $ \db -> updateUnknownMemberAnnounced db vr user m unknownMember memInfo toView $ CRUnknownMemberAnnounced user gInfo m unknownMember updatedMember memberAnnouncedToView updatedMember Right _ -> messageError "x.grp.mem.new error: member already exists" @@ -5221,7 +5422,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = xGrpMemIntro gInfo@GroupInfo {chatSettings} m@GroupMember {memberRole, localDisplayName = c} memInfo@(MemberInfo memId _ memChatVRange _) memRestrictions = do case memberCategory m of GCHostMember -> - withStore' (\db -> runExceptT $ getGroupMemberByMemberId db user gInfo memId) >>= \case + withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case Right _ -> messageError "x.grp.mem.intro ignored: member already exists" Left _ -> do when (memberRole < GRAdmin) $ throwChatError (CEGroupContactRole c) @@ -5230,27 +5431,29 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = groupConnIds <- createConn subMode directConnIds <- case memChatVRange of Nothing -> Just <$> createConn subMode - Just mcvr - | isCompatibleRange (fromChatVRange mcvr) groupNoDirectVRange -> pure Nothing + Just (ChatVersionRange mcvr) + | maxVersion mcvr >= groupDirectInvVersion -> pure Nothing | otherwise -> Just <$> createConn subMode let customUserProfileId = localProfileId <$> incognitoMembershipProfile gInfo - void $ withStore $ \db -> createIntroReMember db user gInfo m memInfo memRestrictions groupConnIds directConnIds customUserProfileId subMode + vr' = vr PQSupportOff + chatV = maybe (minVersion vr') (\peerVR -> vr' `peerConnChatVersion` fromChatVRange peerVR) memChatVRange + void $ withStore $ \db -> createIntroReMember db user gInfo m chatV memInfo memRestrictions groupConnIds directConnIds customUserProfileId subMode _ -> messageError "x.grp.mem.intro can be only sent by host member" where createConn subMode = createAgentConnectionAsync user CFCreateConnGrpMemInv (chatHasNtfs chatSettings) SCMInvitation subMode sendXGrpMemInv :: Int64 -> Maybe ConnReqInvitation -> XGrpMemIntroCont -> m () sendXGrpMemInv hostConnId directConnReq XGrpMemIntroCont {groupId, groupMemberId, memberId, groupConnReq} = do - hostConn <- withStore $ \db -> getConnectionById db user hostConnId + hostConn <- withStore $ \db -> getConnectionById db vr user hostConnId let msg = XGrpMemInv memberId IntroInvitation {groupConnReq, directConnReq} - void $ sendDirectMessage hostConn msg (GroupId groupId) + void $ sendDirectMemberMessage hostConn msg groupId withStore' $ \db -> updateGroupMemberStatusById db userId groupMemberId GSMemIntroInvited xGrpMemInv :: GroupInfo -> GroupMember -> MemberId -> IntroInvitation -> m () xGrpMemInv gInfo@GroupInfo {groupId} m memId introInv = do case memberCategory m of GCInviteeMember -> - withStore' (\db -> runExceptT $ getGroupMemberByMemberId db user gInfo memId) >>= \case + withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case Left _ -> messageError "x.grp.mem.inv error: referenced member does not exist" Right reMember -> do GroupMemberIntro {introId} <- withStore $ \db -> saveIntroInvitation db reMember m introInv @@ -5264,7 +5467,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let GroupMember {memberId = membershipMemId} = membership checkHostRole m memRole toMember <- - withStore' (\db -> runExceptT $ getGroupMemberByMemberId db user gInfo memId) >>= \case + withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case -- TODO if the missed messages are correctly sent as soon as there is connection before anything else is sent -- the situation when member does not exist is an error -- member receiving x.grp.mem.fwd should have also received x.grp.mem.new prior to that. @@ -5275,13 +5478,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = subMode <- chatReadVar subscriptionMode -- [incognito] send membership incognito profile, create direct connection as incognito let membershipProfile = redactedMemberProfile $ fromLocalProfile $ memberProfile membership - dm <- directMessage $ XGrpMemInfo membershipMemId membershipProfile + dm <- encodeConnInfo $ XGrpMemInfo membershipMemId membershipProfile -- [async agent commands] no continuation needed, but commands should be asynchronous for stability groupConnIds <- joinAgentConnectionAsync user (chatHasNtfs chatSettings) groupConnReq dm subMode directConnIds <- forM directConnReq $ \dcr -> joinAgentConnectionAsync user True dcr dm subMode let customUserProfileId = localProfileId <$> incognitoMembershipProfile gInfo mcvr = maybe chatInitialVRange fromChatVRange memChatVRange - withStore' $ \db -> createIntroToMemberContact db user m toMember mcvr groupConnIds directConnIds customUserProfileId subMode + chatV = vr PQSupportOff `peerConnChatVersion` mcvr + withStore' $ \db -> createIntroToMemberContact db user m toMember chatV mcvr groupConnIds directConnIds customUserProfileId subMode xGrpMemRole :: GroupInfo -> GroupMember -> MemberId -> GroupMemberRole -> RcvMessage -> UTCTime -> m () xGrpMemRole gInfo@GroupInfo {membership} m@GroupMember {memberRole = senderRole} memId memRole msg brokerTs @@ -5289,7 +5493,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let gInfo' = gInfo {membership = membership {memberRole = memRole}} in changeMemberRole gInfo' membership $ RGEUserRole memRole | otherwise = - withStore' (\db -> runExceptT $ getGroupMemberByMemberId db user gInfo memId) >>= \case + withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case Right member -> changeMemberRole gInfo member $ RGEMemberRole (groupMemberId' member) (fromLocalProfile $ memberProfile member) memRole Left _ -> messageError "x.grp.mem.role with unknown member ID" where @@ -5318,7 +5522,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- member shouldn't receive this message about themselves messageError "x.grp.mem.restrict: admin blocks you" | otherwise = - withStore' (\db -> runExceptT $ getGroupMemberByMemberId db user gInfo memId) >>= \case + withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case Right bm@GroupMember {groupMemberId = bmId, memberRole, memberProfile = bmp} | senderRole < GRAdmin || senderRole < memberRole -> messageError "x.grp.mem.restrict with insufficient member permissions" | otherwise -> do @@ -5337,12 +5541,12 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = setMemberBlocked bmId = withStore $ \db -> do liftIO $ updateGroupMemberBlocked db user groupId bmId restriction - getGroupMember db user groupId bmId + getGroupMember db vr user groupId bmId blocked = mrsBlocked restriction xGrpMemCon :: GroupInfo -> GroupMember -> MemberId -> m () xGrpMemCon gInfo sendingMember memId = do - refMember <- withStore $ \db -> getGroupMemberByMemberId db user gInfo memId + refMember <- withStore $ \db -> getGroupMemberByMemberId db vr user gInfo memId case (memberCategory sendingMember, memberCategory refMember) of (GCInviteeMember, GCInviteeMember) -> withStore' (\db -> runExceptT $ getIntroduction db refMember sendingMember) >>= \case @@ -5386,13 +5590,13 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = then checkRole membership $ do deleteGroupLinkIfExists user gInfo -- member records are not deleted to keep history - members <- withStore' $ \db -> getGroupMembers db user gInfo + members <- withStore' $ \db -> getGroupMembers db vr user gInfo deleteMembersConnections user members withStore' $ \db -> updateGroupMemberStatus db userId membership GSMemRemoved deleteMemberItem RGEUserDeleted toView $ CRDeletedMemberUser user gInfo {membership = membership {memberStatus = GSMemRemoved}} m else - withStore' (\db -> runExceptT $ getGroupMemberByMemberId db user gInfo memId) >>= \case + withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memId) >>= \case Left _ -> messageError "x.grp.mem.del with unknown member ID" Right member@GroupMember {groupMemberId, memberProfile} -> checkRole member $ do @@ -5424,7 +5628,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = xGrpDel gInfo@GroupInfo {membership} m@GroupMember {memberRole} msg brokerTs = do when (memberRole /= GROwner) $ throwChatError $ CEGroupUserRole gInfo GROwner ms <- withStore' $ \db -> do - members <- getGroupMembers db user gInfo + members <- getGroupMembers db vr user gInfo updateGroupMemberStatus db userId membership GSMemGroupDeleted pure members -- member records are not deleted to keep history @@ -5453,7 +5657,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = case memberContactId of Nothing -> createNewContact subMode Just mContactId -> do - mCt <- withStore $ \db -> getContact db user mContactId + mCt <- withStore $ \db -> getContact db vr user mContactId let Contact {activeConn, contactGrpInvSent} = mCt forM_ activeConn $ \Connection {connId} -> if contactGrpInvSent @@ -5479,7 +5683,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = joinConn subMode = do -- [incognito] send membership incognito profile let p = userProfileToSend user (fromLocalProfile <$> incognitoMembershipProfile g) Nothing False - dm <- directMessage $ XInfo p + dm <- encodeConnInfo $ XInfo p joinAgentConnectionAsync user True connReq dm subMode createItems mCt' m' = do createInternalChatItem user (CDGroupRcv g m') (CIRcvGroupEvent RGEMemberCreatedContact) Nothing @@ -5496,7 +5700,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = xGrpMsgForward :: GroupInfo -> GroupMember -> MemberId -> ChatMessage 'Json -> UTCTime -> m () xGrpMsgForward gInfo@GroupInfo {groupId} m@GroupMember {memberRole, localDisplayName} memberId msg msgTs = do when (memberRole < GRAdmin) $ throwChatError (CEGroupContactRole localDisplayName) - withStore' (\db -> runExceptT $ getGroupMemberByMemberId db user gInfo memberId) >>= \case + withStore' (\db -> runExceptT $ getGroupMemberByMemberId db vr user gInfo memberId) >>= \case Right author -> processForwardedMsg author msg Left (SEGroupMemberNotFoundByMemberId _) -> do unknownAuthor <- createUnknownMember gInfo memberId @@ -5532,7 +5736,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = directMsgReceived :: Contact -> Connection -> MsgMeta -> NonEmpty MsgReceipt -> m () directMsgReceived ct conn@Connection {connId} msgMeta msgRcpts = do - checkIntegrityCreateItem (CDDirectRcv ct) msgMeta + checkIntegrityCreateItem (CDDirectRcv ct) msgMeta `catchChatError` \_ -> pure () forM_ msgRcpts $ \MsgReceipt {agentMsgId, msgRcptStatus} -> do withStore' $ \db -> updateSndMsgDeliveryStatus db connId agentMsgId $ MDSSndRcvd msgRcptStatus updateDirectItemStatus ct conn agentMsgId $ CISSndRcvd msgRcptStatus SSPComplete @@ -5544,7 +5748,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- - getChatItemIdByAgentMsgId to return [ChatItemId] groupMsgReceived :: GroupInfo -> GroupMember -> Connection -> MsgMeta -> NonEmpty MsgReceipt -> m () groupMsgReceived gInfo m conn@Connection {connId} msgMeta msgRcpts = do - checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta + checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta `catchChatError` \_ -> pure () forM_ msgRcpts $ \MsgReceipt {agentMsgId, msgRcptStatus} -> do withStore' $ \db -> updateSndMsgDeliveryStatus db connId agentMsgId $ MDSSndRcvd msgRcptStatus updateGroupItemStatus gInfo m conn agentMsgId $ CISSndRcvd msgRcptStatus SSPComplete @@ -5598,45 +5802,85 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = toView $ CRChatItemStatusUpdated user (AChatItem SCTGroup SMDSnd (GroupChat gInfo) chatItem) _ -> pure () +createContactPQSndItem :: ChatMonad m => User -> Contact -> Connection -> PQEncryption -> m (Contact, Connection) +createContactPQSndItem user ct conn@Connection {pqSndEnabled} pqSndEnabled' = + flip catchChatError (const $ pure (ct, conn)) $ case (pqSndEnabled, pqSndEnabled') of + (Just b, b') | b' /= b -> createPQItem $ CISndConnEvent (SCEPqEnabled pqSndEnabled') + (Nothing, PQEncOn) -> createPQItem $ CISndDirectE2EEInfo (E2EInfo pqSndEnabled') + _ -> pure (ct, conn) + where + createPQItem ciContent = do + let conn' = conn {pqSndEnabled = Just pqSndEnabled'} :: Connection + ct' = ct {activeConn = Just conn'} :: Contact + when (contactPQEnabled ct /= contactPQEnabled ct') $ do + createInternalChatItem user (CDDirectSnd ct') ciContent Nothing + toView $ CRContactPQEnabled user ct' pqSndEnabled' + pure (ct', conn') + +updateContactPQRcv :: ChatMonad m => User -> Contact -> Connection -> PQEncryption -> m (Contact, Connection) +updateContactPQRcv user ct conn@Connection {connId, pqRcvEnabled} pqRcvEnabled' = + flip catchChatError (const $ pure (ct, conn)) $ case (pqRcvEnabled, pqRcvEnabled') of + (Just b, b') | b' /= b -> updatePQ $ CIRcvConnEvent (RCEPqEnabled pqRcvEnabled') + (Nothing, PQEncOn) -> updatePQ $ CIRcvDirectE2EEInfo (E2EInfo pqRcvEnabled') + _ -> pure (ct, conn) + where + updatePQ ciContent = do + withStore' $ \db -> updateConnPQRcvEnabled db connId pqRcvEnabled' + let conn' = conn {pqRcvEnabled = Just pqRcvEnabled'} :: Connection + ct' = ct {activeConn = Just conn'} :: Contact + when (contactPQEnabled ct /= contactPQEnabled ct') $ do + createInternalChatItem user (CDDirectRcv ct') ciContent Nothing + toView $ CRContactPQEnabled user ct' pqRcvEnabled' + pure (ct', conn') + metaBrokerTs :: MsgMeta -> UTCTime metaBrokerTs MsgMeta {broker = (_, brokerTs)} = brokerTs sameMemberId :: MemberId -> GroupMember -> Bool sameMemberId memId GroupMember {memberId} = memId == memberId -updatePeerChatVRange :: ChatMonad m => Connection -> VersionRange -> m Connection -updatePeerChatVRange conn@Connection {connId, peerChatVRange} msgChatVRange = do - let jMsgChatVRange = JVersionRange msgChatVRange - if jMsgChatVRange /= peerChatVRange +-- TODO v5.7 for contacts only version upgrade should trigger enabling PQ support/encryption +updatePeerChatVRange :: ChatMonad m => Connection -> VersionRangeChat -> m Connection +updatePeerChatVRange conn@Connection {connId, connChatVersion = v, peerChatVRange, pqSupport} msgVRange = do + v' <- upgradedConnVersion pqSupport v msgVRange + if msgVRange /= peerChatVRange || v' /= v then do - withStore' $ \db -> setPeerChatVRange db connId msgChatVRange - pure conn {peerChatVRange = jMsgChatVRange} + withStore' $ \db -> setPeerChatVRange db connId v' msgVRange + pure conn {connChatVersion = v', peerChatVRange = msgVRange} else pure conn -updateMemberChatVRange :: ChatMonad m => GroupMember -> Connection -> VersionRange -> m (GroupMember, Connection) -updateMemberChatVRange mem@GroupMember {groupMemberId} conn@Connection {connId, peerChatVRange} msgChatVRange = do - let jMsgChatVRange = JVersionRange msgChatVRange - if jMsgChatVRange /= peerChatVRange +updateMemberChatVRange :: ChatMonad m => GroupMember -> Connection -> VersionRangeChat -> m (GroupMember, Connection) +updateMemberChatVRange mem@GroupMember {groupMemberId} conn@Connection {connId, connChatVersion = v, peerChatVRange} msgVRange = do + v' <- upgradedConnVersion PQSupportOff v msgVRange + if msgVRange /= peerChatVRange || v' /= v then do withStore' $ \db -> do - setPeerChatVRange db connId msgChatVRange - setMemberChatVRange db groupMemberId msgChatVRange - let conn' = conn {peerChatVRange = jMsgChatVRange} - pure (mem {memberChatVRange = jMsgChatVRange, activeConn = Just conn'}, conn') + setPeerChatVRange db connId v' msgVRange + setMemberChatVRange db groupMemberId msgVRange + let conn' = conn {connChatVersion = v', peerChatVRange = msgVRange} + pure (mem {memberChatVRange = msgVRange, activeConn = Just conn'}, conn') else pure (mem, conn) +upgradedConnVersion :: ChatMonad' m => PQSupport -> VersionChat -> VersionRangeChat -> m VersionChat +upgradedConnVersion pqSup v peerVR = do + vr <- chatVersionRange + -- don't allow reducing agreed connection version + pure $ maybe v (\(Compatible v') -> max v v') $ vr pqSup `compatibleVersion` peerVR + parseFileDescription :: (ChatMonad m, FilePartyI p) => Text -> m (ValidFileDescription p) parseFileDescription = liftEither . first (ChatError . CEInvalidFileDescription) . (strDecode . encodeUtf8) -sendDirectFileInline :: ChatMonad m => Contact -> FileTransferMeta -> SharedMsgId -> m () -sendDirectFileInline ct ft sharedMsgId = do - msgDeliveryId <- sendFileInline_ ft sharedMsgId $ sendDirectContactMessage ct +sendDirectFileInline :: ChatMonad m => User -> Contact -> FileTransferMeta -> SharedMsgId -> m () +sendDirectFileInline user ct ft sharedMsgId = do + msgDeliveryId <- sendFileInline_ ft sharedMsgId $ sendDirectContactMessage user ct withStore $ \db -> updateSndDirectFTDelivery db ct ft msgDeliveryId sendMemberFileInline :: ChatMonad m => GroupMember -> Connection -> FileTransferMeta -> SharedMsgId -> m () sendMemberFileInline m@GroupMember {groupId} conn ft sharedMsgId = do - msgDeliveryId <- sendFileInline_ ft sharedMsgId $ \msg -> sendDirectMessage conn msg $ GroupId groupId + msgDeliveryId <- sendFileInline_ ft sharedMsgId $ \msg -> do + (sndMsg, msgDeliveryId, _) <- sendDirectMemberMessage conn msg groupId + pure (sndMsg, msgDeliveryId) withStore' $ \db -> updateSndGroupFTDelivery db m conn ft msgDeliveryId sendFileInline_ :: ChatMonad m => FileTransferMeta -> SharedMsgId -> (ChatMsgEvent 'Binary -> m (SndMessage, Int64)) -> m Int64 @@ -5678,7 +5922,7 @@ sendFileChunk user ft@SndFileTransfer {fileId, fileStatus, agentConnId = AgentCo sendFileChunkNo :: ChatMonad m => SndFileTransfer -> Integer -> m () sendFileChunkNo ft@SndFileTransfer {agentConnId = AgentConnId acId} chunkNo = do chunkBytes <- readFileChunk ft chunkNo - msgId <- withAgent $ \a -> sendMessage a acId SMP.noMsgFlags $ smpEncode FileChunk {chunkNo, chunkBytes} + (msgId, _) <- withAgent $ \a -> sendMessage a acId PQEncOff SMP.noMsgFlags $ smpEncode FileChunk {chunkNo, chunkBytes} withStore' $ \db -> updateSndFileChunkMsg db ft chunkNo msgId readFileChunk :: ChatMonad m => SndFileTransfer -> Integer -> m ByteString @@ -5773,6 +6017,7 @@ cancelSndFile user FileTransferMeta {fileId, xftpSndFile} fts sendCancel = do agentXFTPDeleteSndFileRemote user xsf fileId `catchChatError` (toView . CRChatError (Just user)) pure [] +-- TODO v6.0 remove cancelSndFileTransfer :: ChatMonad m => User -> SndFileTransfer -> Bool -> m (Maybe ConnId) cancelSndFileTransfer user@User {userId} ft@SndFileTransfer {fileId, connId, agentConnId = AgentConnId acId, fileStatus, fileInline} sendCancel = if fileStatus == FSCancelled || fileStatus == FSComplete @@ -5785,9 +6030,10 @@ cancelSndFileTransfer user@User {userId} ft@SndFileTransfer {fileId, connId, age deleteSndFileChunks db ft when sendCancel $ case fileInline of Just _ -> do - (sharedMsgId, conn) <- withStore $ \db -> (,) <$> getSharedMsgIdByFileId db userId fileId <*> getConnectionById db user connId - void . sendDirectMessage conn (BFileChunk sharedMsgId FileChunkCancel) $ ConnectionId connId - _ -> withAgent $ \a -> void . sendMessage a acId SMP.noMsgFlags $ smpEncode FileChunkCancel + vr <- chatVersionRange + (sharedMsgId, conn) <- withStore $ \db -> (,) <$> getSharedMsgIdByFileId db userId fileId <*> getConnectionById db vr user connId + void $ sendDirectMessage_ conn PQSupportOff (BFileChunk sharedMsgId FileChunkCancel) (ConnectionId connId) + _ -> withAgent $ \a -> void . sendMessage a acId PQEncOff SMP.noMsgFlags $ smpEncode FileChunkCancel pure fileConnId fileConnId = if isNothing fileInline then Just acId else Nothing @@ -5798,17 +6044,23 @@ closeFileHandle fileId files = do liftIO $ mapM_ hClose h_ `catchAll_` pure () deleteMembersConnections :: ChatMonad m => User -> [GroupMember] -> m () -deleteMembersConnections user members = do +deleteMembersConnections user members = deleteMembersConnections' user members False + +deleteMembersConnections' :: ChatMonad m => User -> [GroupMember] -> Bool -> m () +deleteMembersConnections' user members waitDelivery = do let memberConns = filter (\Connection {connStatus} -> connStatus /= ConnDeleted) $ mapMaybe (\GroupMember {activeConn} -> activeConn) members - deleteAgentConnectionsAsync user $ map aConnId memberConns - forM_ memberConns $ \conn -> withStore' $ \db -> updateConnectionStatus db conn ConnDeleted + deleteAgentConnectionsAsync' user (map aConnId memberConns) waitDelivery + void . withStoreBatch' $ \db -> map (\conn -> updateConnectionStatus db conn ConnDeleted) memberConns deleteMemberConnection :: ChatMonad m => User -> GroupMember -> m () -deleteMemberConnection user GroupMember {activeConn} = do +deleteMemberConnection user mem = deleteMemberConnection' user mem False + +deleteMemberConnection' :: ChatMonad m => User -> GroupMember -> Bool -> m () +deleteMemberConnection' user GroupMember {activeConn} waitDelivery = do forM_ activeConn $ \conn -> do - deleteAgentConnectionAsync user $ aConnId conn + deleteAgentConnectionAsync' user (aConnId conn) waitDelivery withStore' $ \db -> updateConnectionStatus db conn ConnDeleted deleteOrUpdateMemberRecord :: ChatMonad m => User -> GroupMember -> m () @@ -5818,10 +6070,13 @@ deleteOrUpdateMemberRecord user@User {userId} member = Just _ -> updateGroupMemberStatus db userId member GSMemRemoved Nothing -> deleteGroupMember db user member -sendDirectContactMessage :: (MsgEncodingI e, ChatMonad m) => Contact -> ChatMsgEvent e -> m (SndMessage, Int64) -sendDirectContactMessage ct chatMsgEvent = do - conn@Connection {connId} <- liftEither $ contactSendConn_ ct - sendDirectMessage conn chatMsgEvent (ConnectionId connId) +sendDirectContactMessage :: (MsgEncodingI e, ChatMonad m) => User -> Contact -> ChatMsgEvent e -> m (SndMessage, Int64) +sendDirectContactMessage user ct chatMsgEvent = do + conn@Connection {connId, pqSupport} <- liftEither $ contactSendConn_ ct + r <- sendDirectMessage_ conn pqSupport chatMsgEvent (ConnectionId connId) + let (sndMessage, msgDeliveryId, pqEnc') = r + void $ createContactPQSndItem user ct conn pqEnc' + pure (sndMessage, msgDeliveryId) contactSendConn_ :: Contact -> Either ChatError Connection contactSendConn_ ct@Contact {activeConn} = case activeConn of @@ -5834,84 +6089,142 @@ contactSendConn_ ct@Contact {activeConn} = case activeConn of where err = Left . ChatError -sendDirectMessage :: (MsgEncodingI e, ChatMonad m) => Connection -> ChatMsgEvent e -> ConnOrGroupId -> m (SndMessage, Int64) -sendDirectMessage conn chatMsgEvent connOrGroupId = do +-- unlike sendGroupMemberMessage, this function will not store message as pending +-- TODO v5.8 we could remove pending messages once all clients support forwarding +sendDirectMemberMessage :: (MsgEncodingI e, ChatMonad m) => Connection -> ChatMsgEvent e -> GroupId -> m (SndMessage, Int64, PQEncryption) +sendDirectMemberMessage conn chatMsgEvent groupId = sendDirectMessage_ conn PQSupportOff chatMsgEvent (GroupId groupId) + +sendDirectMessage_ :: (MsgEncodingI e, ChatMonad m) => Connection -> PQSupport -> ChatMsgEvent e -> ConnOrGroupId -> m (SndMessage, Int64, PQEncryption) +sendDirectMessage_ conn pqSup chatMsgEvent connOrGroupId = do when (connDisabled conn) $ throwChatError (CEConnectionDisabled conn) - msg@SndMessage {msgId, msgBody} <- createSndMessage chatMsgEvent connOrGroupId - (msg,) <$> deliverMessage conn (toCMEventTag chatMsgEvent) msgBody msgId + msg@SndMessage {msgId, msgBody} <- createSndMessage chatMsgEvent connOrGroupId pqSup + -- TODO move compressed body to SndMessage and compress in createSndMessage + (msgDeliveryId, pqEnc') <- deliverMessage conn (toCMEventTag chatMsgEvent) msgBody msgId + pure (msg, msgDeliveryId, pqEnc') -createSndMessage :: (MsgEncodingI e, ChatMonad m) => ChatMsgEvent e -> ConnOrGroupId -> m SndMessage -createSndMessage chatMsgEvent connOrGroupId = - liftEither . runIdentity =<< createSndMessages (Identity (connOrGroupId, chatMsgEvent)) +createSndMessage :: (MsgEncodingI e, ChatMonad m) => ChatMsgEvent e -> ConnOrGroupId -> PQSupport -> m SndMessage +createSndMessage chatMsgEvent connOrGroupId pqSup = + liftEither . runIdentity =<< createSndMessages (Identity (connOrGroupId, pqSup, chatMsgEvent)) -createSndMessages :: forall e m t. (MsgEncodingI e, ChatMonad' m, Traversable t) => t (ConnOrGroupId, ChatMsgEvent e) -> m (t (Either ChatError SndMessage)) +createSndMessages :: forall e m t. (MsgEncodingI e, ChatMonad' m, Traversable t) => t (ConnOrGroupId, PQSupport, ChatMsgEvent e) -> m (t (Either ChatError SndMessage)) createSndMessages idsEvents = do - gVar <- asks random + g <- asks random vr <- chatVersionRange - withStoreBatch $ \db -> fmap (uncurry (createMsg db gVar vr)) idsEvents + withStoreBatch $ \db -> fmap (createMsg db g vr) idsEvents where - createMsg db gVar chatVRange connOrGroupId evnt = runExceptT $ do - withExceptT ChatErrorStore $ createNewSndMessage db gVar connOrGroupId evnt (encodeMessage chatVRange evnt) - encodeMessage chatVRange evnt sharedMsgId = - encodeChatMessage ChatMessage {chatVRange, msgId = Just sharedMsgId, chatMsgEvent = evnt} + createMsg :: DB.Connection -> TVar ChaChaDRG -> (PQSupport -> VersionRangeChat) -> (ConnOrGroupId, PQSupport, ChatMsgEvent e) -> IO (Either ChatError SndMessage) + createMsg db g vr (connOrGroupId, pqSup, evnt) = runExceptT $ do + withExceptT ChatErrorStore $ createNewSndMessage db g connOrGroupId evnt encodeMessage + where + encodeMessage sharedMsgId = + encodeChatMessage maxEncodedMsgLength ChatMessage {chatVRange = vr pqSup, msgId = Just sharedMsgId, chatMsgEvent = evnt} sendGroupMemberMessages :: forall e m. (MsgEncodingI e, ChatMonad m) => User -> Connection -> NonEmpty (ChatMsgEvent e) -> GroupId -> m () -sendGroupMemberMessages user conn@Connection {connId} events groupId = do +sendGroupMemberMessages user conn events groupId = do when (connDisabled conn) $ throwChatError (CEConnectionDisabled conn) - let idsEvts = L.map (GroupId groupId,) events + let idsEvts = L.map (GroupId groupId,PQSupportOff,) events (errs, msgs) <- partitionEithers . L.toList <$> createSndMessages idsEvts unless (null errs) $ toView $ CRChatErrors (Just user) errs - unless (null msgs) $ do - let (errs', msgBatches) = partitionEithers $ batchMessages maxChatMsgSize msgs + forM_ (L.nonEmpty msgs) $ \msgs' -> do + -- TODO v5.7 based on version (?) + -- let shouldCompress = False + -- let batched = if shouldCompress then batchSndMessagesBinary msgs' else batchSndMessagesJSON msgs' + let batched = batchSndMessagesJSON msgs' + let (errs', msgBatches) = partitionEithers batched -- shouldn't happen, as large messages would have caused createNewSndMessage to throw SELargeMsg unless (null errs') $ toView $ CRChatErrors (Just user) errs' forM_ msgBatches $ \batch -> - processBatch batch `catchChatError` (toView . CRChatError (Just user)) - where - processBatch :: MsgBatch -> m () - processBatch (MsgBatch batchBody sndMsgs) = do - agentMsgId <- withAgent $ \a -> sendMessage a (aConnId conn) MsgFlags {notification = True} batchBody - let sndMsgDelivery = SndMsgDelivery {connId, agentMsgId} - void . withStoreBatch' $ \db -> map (\SndMessage {msgId} -> createSndMsgDelivery db sndMsgDelivery msgId) sndMsgs + processSndMessageBatch conn batch `catchChatError` (toView . CRChatError (Just user)) -directMessage :: (MsgEncodingI e, ChatMonad m) => ChatMsgEvent e -> m ByteString -directMessage chatMsgEvent = do - chatVRange <- chatVersionRange - let r = encodeChatMessage ChatMessage {chatVRange, msgId = Nothing, chatMsgEvent} - case r of - ECMEncoded encodedBody -> pure encodedBody - ECMLarge -> throwChatError $ CEException "large message" +processSndMessageBatch :: ChatMonad m => Connection -> MsgBatch -> m () +processSndMessageBatch conn@Connection {connId} (MsgBatch batchBody sndMsgs) = do + (agentMsgId, _pqEnc) <- withAgent $ \a -> sendMessage a (aConnId conn) PQEncOff MsgFlags {notification = True} batchBody + let sndMsgDelivery = SndMsgDelivery {connId, agentMsgId} + void . withStoreBatch' $ \db -> map (\SndMessage {msgId} -> createSndMsgDelivery db sndMsgDelivery msgId) sndMsgs -deliverMessage :: ChatMonad m => Connection -> CMEventTag e -> MsgBody -> MessageId -> m Int64 +-- TODO v5.7 update batching for groups +batchSndMessagesJSON :: NonEmpty SndMessage -> [Either ChatError MsgBatch] +batchSndMessagesJSON = batchMessages maxEncodedMsgLength . L.toList + +-- batchSndMessagesBinary :: NonEmpty SndMessage -> [Either ChatError MsgBatch] +-- batchSndMessagesBinary msgs = map toMsgBatch . SMP.batchTransmissions_ (maxEncodedMsgLength) $ L.zip (map compress1 msgs) msgs +-- where +-- toMsgBatch :: SMP.TransportBatch SndMessage -> Either ChatError MsgBatch +-- toMsgBatch = \case +-- SMP.TBTransmissions combined _n sms -> Right $ MsgBatch (markCompressedBatch combined) sms +-- SMP.TBError tbe SndMessage {msgId} -> Left . ChatError $ CEInternalError (show tbe <> " " <> show msgId) +-- SMP.TBTransmission {} -> Left . ChatError $ CEInternalError "batchTransmissions_ didn't produce a batch" + +encodeConnInfo :: (MsgEncodingI e, ChatMonad m) => ChatMsgEvent e -> m ByteString +encodeConnInfo chatMsgEvent = do + vr <- chatVersionRange + encodeConnInfoPQ PQSupportOff (maxVersion $ vr PQSupportOff) chatMsgEvent + +encodeConnInfoPQ :: (MsgEncodingI e, ChatMonad m) => PQSupport -> VersionChat -> ChatMsgEvent e -> m ByteString +encodeConnInfoPQ pqSup v chatMsgEvent = do + vr <- chatVersionRange + let info = ChatMessage {chatVRange = vr pqSup, msgId = Nothing, chatMsgEvent} + case encodeChatMessage maxEncodedInfoLength info of + ECMEncoded connInfo -> case pqSup of + PQSupportOn | v >= pqEncryptionCompressionVersion && B.length connInfo > maxCompressedInfoLength -> do + let connInfo' = compressedBatchMsgBody_ connInfo + when (B.length connInfo' > maxCompressedInfoLength) $ throwChatError $ CEException "large compressed info" + pure connInfo' + _ -> pure connInfo + ECMLarge -> throwChatError $ CEException "large info" + +deliverMessage :: ChatMonad m => Connection -> CMEventTag e -> MsgBody -> MessageId -> m (Int64, PQEncryption) deliverMessage conn cmEventTag msgBody msgId = do let msgFlags = MsgFlags {notification = hasNotification cmEventTag} deliverMessage' conn msgFlags msgBody msgId -deliverMessage' :: ChatMonad m => Connection -> MsgFlags -> MsgBody -> MessageId -> m Int64 +deliverMessage' :: ChatMonad m => Connection -> MsgFlags -> MsgBody -> MessageId -> m (Int64, PQEncryption) deliverMessage' conn msgFlags msgBody msgId = - deliverMessages [(conn, msgFlags, msgBody, msgId)] >>= \case - [r] -> liftEither r + deliverMessages ((conn, msgFlags, msgBody, msgId) :| []) >>= \case + r :| [] -> liftEither r rs -> throwChatError $ CEInternalError $ "deliverMessage: expected 1 result, got " <> show (length rs) type MsgReq = (Connection, MsgFlags, MsgBody, MessageId) -deliverMessages :: ChatMonad' m => [MsgReq] -> m [Either ChatError Int64] -deliverMessages = deliverMessagesB . map Right +deliverMessages :: ChatMonad' m => NonEmpty MsgReq -> m (NonEmpty (Either ChatError (Int64, PQEncryption))) +deliverMessages msgs = deliverMessagesB $ L.map Right msgs -deliverMessagesB :: ChatMonad' m => [Either ChatError MsgReq] -> m [Either ChatError Int64] +deliverMessagesB :: forall m. ChatMonad' m => NonEmpty (Either ChatError MsgReq) -> m (NonEmpty (Either ChatError (Int64, PQEncryption))) deliverMessagesB msgReqs = do - sent <- zipWith prepareBatch msgReqs <$> withAgent' (`sendMessagesB` map toAgent msgReqs) - withStoreBatch $ \db -> map (bindRight $ createDelivery db) sent + msgReqs' <- liftIO compressBodies + sent <- L.zipWith prepareBatch msgReqs' <$> withAgent' (`sendMessagesB` L.map toAgent msgReqs') + void $ withStoreBatch' $ \db -> map (updatePQSndEnabled db) (rights . L.toList $ sent) + withStoreBatch $ \db -> L.map (bindRight $ createDelivery db) sent where + compressBodies = + forME msgReqs $ \mr@(conn@Connection {pqSupport, connChatVersion = v}, msgFlags, msgBody, msgId) -> + runExceptT $ case pqSupport of + -- we only compress messages when: + -- 1) PQ support is enabled + -- 2) version is compatible with compression + -- 3) message is longer than max compressed size (as this function is not used for batched messages anyway) + PQSupportOn | v >= pqEncryptionCompressionVersion && B.length msgBody > maxCompressedMsgLength -> do + let msgBody' = compressedBatchMsgBody_ msgBody + when (B.length msgBody' > maxCompressedMsgLength) $ throwError $ ChatError $ CEException "large compressed message" + pure (conn, msgFlags, msgBody', msgId) + _ -> pure mr toAgent = \case - Right (conn, msgFlags, msgBody, _msgId) -> Right (aConnId conn, msgFlags, msgBody) + Right (conn@Connection {pqEncryption}, msgFlags, msgBody, _msgId) -> Right (aConnId conn, pqEncryption, msgFlags, msgBody) Left _ce -> Left (AP.INTERNAL "ChatError, skip") -- as long as it is Left, the agent batchers should just step over it prepareBatch (Right req) (Right ar) = Right (req, ar) prepareBatch (Left ce) _ = Left ce -- restore original ChatError prepareBatch _ (Left ae) = Left $ ChatErrorAgent ae Nothing - createDelivery :: DB.Connection -> (MsgReq, AgentMsgId) -> IO (Either ChatError Int64) - createDelivery db ((Connection {connId}, _, _, msgId), agentMsgId) = - Right <$> createSndMsgDelivery db (SndMsgDelivery {connId, agentMsgId}) msgId + createDelivery :: DB.Connection -> (MsgReq, (AgentMsgId, PQEncryption)) -> IO (Either ChatError (Int64, PQEncryption)) + createDelivery db ((Connection {connId}, _, _, msgId), (agentMsgId, pqEnc')) = + Right . (,pqEnc') <$> createSndMsgDelivery db (SndMsgDelivery {connId, agentMsgId}) msgId + updatePQSndEnabled :: DB.Connection -> (MsgReq, (AgentMsgId, PQEncryption)) -> IO () + updatePQSndEnabled db ((Connection {connId, pqSndEnabled}, _, _, _), (_, pqSndEnabled')) = + case (pqSndEnabled, pqSndEnabled') of + (Just b, b') | b' /= b -> updatePQ + (Nothing, PQEncOn) -> updatePQ + _ -> pure () + where + updatePQ = updateConnPQSndEnabled db connId pqSndEnabled' sendGroupMessage :: (MsgEncodingI e, ChatMonad m) => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> m (SndMessage, [GroupMember]) sendGroupMessage user gInfo members chatMsgEvent = do @@ -5929,7 +6242,7 @@ sendGroupMessage user gInfo members chatMsgEvent = do (Nothing, Just _) -> True _ -> False sendProfileUpdate = do - let members' = filter (\m -> isCompatibleRange (memberChatVRange' m) memberProfileUpdateVRange) members + let members' = filter (`supportsVersion` memberProfileUpdateVersion) members profileUpdateEvent = XInfo $ redactedMemberProfile $ fromLocalProfile p void $ sendGroupMessage' user gInfo members' profileUpdateEvent currentTs <- liftIO getCurrentTime @@ -5937,12 +6250,13 @@ sendGroupMessage user gInfo members chatMsgEvent = do sendGroupMessage' :: (MsgEncodingI e, ChatMonad m) => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> m (SndMessage, [GroupMember]) sendGroupMessage' user GroupInfo {groupId} members chatMsgEvent = do - msg@SndMessage {msgId, msgBody} <- createSndMessage chatMsgEvent (GroupId groupId) + msg@SndMessage {msgId, msgBody} <- createSndMessage chatMsgEvent (GroupId groupId) PQSupportOff recipientMembers <- liftIO $ shuffleMembers (filter memberCurrent members) let msgFlags = MsgFlags {notification = hasNotification $ toCMEventTag chatMsgEvent} (toSend, pending) = foldr addMember ([], []) recipientMembers + -- TODO PQ either somehow ensure that group members connections cannot have pqSupport/pqEncryption or pass Off's here msgReqs = map (\(_, conn) -> (conn, msgFlags, msgBody, msgId)) toSend - delivered <- deliverMessages msgReqs + delivered <- maybe (pure []) (fmap L.toList . deliverMessages) $ L.nonEmpty msgReqs let errors = lefts delivered unless (null errors) $ toView $ CRChatErrors (Just user) errors stored <- withStoreBatch' $ \db -> map (\m -> createPendingGroupMessage db (groupMemberId' m) msgId Nothing) pending @@ -5977,16 +6291,12 @@ memberSendAction chatMsgEvent members m@GroupMember {invitedByGroupMemberId} = c | isXGrpMsgForward chatMsgEvent = Nothing | otherwise = Just MSAPending where - forwardSupported = - let mcvr = memberChatVRange' m - in isCompatibleRange mcvr groupForwardVRange && invitingMemberSupportsForward + forwardSupported = m `supportsVersion` groupForwardVersion && invitingMemberSupportsForward invitingMemberSupportsForward = case invitedByGroupMemberId of Just invMemberId -> -- can be optimized for large groups by replacing [GroupMember] with Map GroupMemberId GroupMember case find (\m' -> groupMemberId' m' == invMemberId) members of - Just invitingMember -> do - let mcvr = memberChatVRange' invitingMember - isCompatibleRange mcvr groupForwardVRange + Just invitingMember -> invitingMember `supportsVersion` groupForwardVersion Nothing -> False Nothing -> False isXGrpMsgForward ev = case ev of @@ -5995,7 +6305,7 @@ memberSendAction chatMsgEvent members m@GroupMember {invitedByGroupMemberId} = c sendGroupMemberMessage :: forall e m. (MsgEncodingI e, ChatMonad m) => User -> GroupMember -> ChatMsgEvent e -> Int64 -> Maybe Int64 -> m () -> m () sendGroupMemberMessage user m@GroupMember {groupMemberId} chatMsgEvent groupId introId_ postDeliver = do - msg <- createSndMessage chatMsgEvent (GroupId groupId) + msg <- createSndMessage chatMsgEvent (GroupId groupId) PQSupportOff messageMember msg `catchChatError` (\e -> toView (CRChatError (Just user) e)) where messageMember :: SndMessage -> m () @@ -6043,9 +6353,10 @@ saveGroupRcvMsg user groupId authorMember conn@Connection {connId} agentMsgMeta withStore (\db -> createNewMessageAndRcvMsgDelivery db (GroupId groupId) newMsg sharedMsgId_ rcvMsgDelivery $ Just amGroupMemId) `catchChatError` \e -> case e of ChatErrorStore (SEDuplicateGroupMessage _ _ _ (Just forwardedByGroupMemberId)) -> do - fm <- withStore $ \db -> getGroupMember db user groupId forwardedByGroupMemberId + vr <- chatVersionRange + fm <- withStore $ \db -> getGroupMember db vr user groupId forwardedByGroupMemberId forM_ (memberConn fm) $ \fmConn -> - void $ sendDirectMessage fmConn (XGrpMemCon amMemId) (GroupId groupId) + void $ sendDirectMemberMessage fmConn (XGrpMemCon amMemId) groupId throwError e _ -> throwError e pure (am', conn', msg) @@ -6058,10 +6369,11 @@ saveGroupFwdRcvMsg user groupId forwardingMember refAuthorMember@GroupMember {me withStore (\db -> createNewRcvMessage db (GroupId groupId) newMsg sharedMsgId_ refAuthorId fwdMemberId) `catchChatError` \e -> case e of ChatErrorStore (SEDuplicateGroupMessage _ _ (Just authorGroupMemberId) Nothing) -> do - am@GroupMember {memberId = amMemberId} <- withStore $ \db -> getGroupMember db user groupId authorGroupMemberId + vr <- chatVersionRange + am@GroupMember {memberId = amMemberId} <- withStore $ \db -> getGroupMember db vr user groupId authorGroupMemberId if sameMemberId refMemberId am then forM_ (memberConn forwardingMember) $ \fmConn -> - void $ sendDirectMessage fmConn (XGrpMemCon amMemberId) (GroupId groupId) + void $ sendDirectMemberMessage fmConn (XGrpMemCon amMemberId) groupId else toView $ CRMessageError user "error" "saveGroupFwdRcvMsg: referenced author member id doesn't match message member id" throwError e _ -> throwError e @@ -6103,13 +6415,13 @@ mkChatItem cd ciId content file quotedItem sharedMsgId itemTimed live itemTs for deleteDirectCI :: (ChatMonad m, MsgDirectionI d) => User -> Contact -> ChatItem 'CTDirect d -> Bool -> Bool -> m ChatResponse deleteDirectCI user ct ci@ChatItem {file} byUser timed = do deleteCIFile user file - withStoreCtx' (Just "deleteDirectCI, deleteDirectChatItem") $ \db -> deleteDirectChatItem db user ct ci + withStore' $ \db -> deleteDirectChatItem db user ct ci pure $ CRChatItemDeleted user (AChatItem SCTDirect msgDirection (DirectChat ct) ci) Nothing byUser timed deleteGroupCI :: (ChatMonad m, MsgDirectionI d) => User -> GroupInfo -> ChatItem 'CTGroup d -> Bool -> Bool -> Maybe GroupMember -> UTCTime -> m ChatResponse deleteGroupCI user gInfo ci@ChatItem {file} byUser timed byGroupMember_ deletedTs = do deleteCIFile user file - toCi <- withStoreCtx' (Just "deleteGroupCI, deleteGroupChatItem ...") $ \db -> + toCi <- withStore' $ \db -> case byGroupMember_ of Nothing -> deleteGroupChatItem db user gInfo ci $> Nothing Just m -> Just <$> updateGroupChatItemModerated db user gInfo ci m deletedTs @@ -6118,18 +6430,19 @@ deleteGroupCI user gInfo ci@ChatItem {file} byUser timed byGroupMember_ deletedT gItem = AChatItem SCTGroup msgDirection (GroupChat gInfo) deleteLocalCI :: (ChatMonad m, MsgDirectionI d) => User -> NoteFolder -> ChatItem 'CTLocal d -> Bool -> Bool -> m ChatResponse -deleteLocalCI user nf ci@ChatItem {file} byUser timed = do - forM_ file $ \CIFile {fileSource} -> do - forM_ (CF.filePath <$> fileSource) $ \fPath -> - deleteFileLocally fPath `catchChatError` (toView . CRChatError (Just user)) +deleteLocalCI user nf ci@ChatItem {file = file_} byUser timed = do + forM_ file_ $ \file -> do + let filesInfo = [mkCIFileInfo file] + deleteFilesLocally filesInfo withStore' $ \db -> deleteLocalChatItem db user nf ci pure $ CRChatItemDeleted user (AChatItem SCTLocal msgDirection (LocalChat nf) ci) Nothing byUser timed deleteCIFile :: (ChatMonad m, MsgDirectionI d) => User -> Maybe (CIFile d) -> m () deleteCIFile user file_ = forM_ file_ $ \file -> do - fileAgentConnIds <- deleteFile' user (mkCIFileInfo file) True - deleteAgentConnectionsAsync user fileAgentConnIds + let filesInfo = [mkCIFileInfo file] + cancelFilesInProgress user filesInfo + deleteFilesLocally filesInfo markDirectCIDeleted :: (ChatMonad m, MsgDirectionI d) => User -> Contact -> ChatItem 'CTDirect d -> MessageId -> Bool -> UTCTime -> m ChatResponse markDirectCIDeleted user ct ci@ChatItem {file} msgId byUser deletedTs = do @@ -6150,56 +6463,92 @@ markGroupCIDeleted user gInfo ci@ChatItem {file} msgId byUser byGroupMember_ del cancelCIFile :: (ChatMonad m, MsgDirectionI d) => User -> Maybe (CIFile d) -> m () cancelCIFile user file_ = forM_ file_ $ \file -> do - fileAgentConnIds <- cancelFile' user (mkCIFileInfo file) True - deleteAgentConnectionsAsync user fileAgentConnIds + let filesInfo = [mkCIFileInfo file] + cancelFilesInProgress user filesInfo createAgentConnectionAsync :: forall m c. (ChatMonad m, ConnectionModeI c) => User -> CommandFunction -> Bool -> SConnectionMode c -> SubscriptionMode -> m (CommandId, ConnId) createAgentConnectionAsync user cmdFunction enableNtfs cMode subMode = do cmdId <- withStore' $ \db -> createCommand db user Nothing cmdFunction - connId <- withAgent $ \a -> createConnectionAsync a (aUserId user) (aCorrId cmdId) enableNtfs cMode subMode + connId <- withAgent $ \a -> createConnectionAsync a (aUserId user) (aCorrId cmdId) enableNtfs cMode IKPQOff subMode pure (cmdId, connId) joinAgentConnectionAsync :: ChatMonad m => User -> Bool -> ConnectionRequestUri c -> ConnInfo -> SubscriptionMode -> m (CommandId, ConnId) joinAgentConnectionAsync user enableNtfs cReqUri cInfo subMode = do cmdId <- withStore' $ \db -> createCommand db user Nothing CFJoinConn - connId <- withAgent $ \a -> joinConnectionAsync a (aUserId user) (aCorrId cmdId) enableNtfs cReqUri cInfo subMode + connId <- withAgent $ \a -> joinConnectionAsync a (aUserId user) (aCorrId cmdId) enableNtfs cReqUri cInfo PQSupportOff subMode pure (cmdId, connId) allowAgentConnectionAsync :: (MsgEncodingI e, ChatMonad m) => User -> Connection -> ConfirmationId -> ChatMsgEvent e -> m () -allowAgentConnectionAsync user conn@Connection {connId} confId msg = do +allowAgentConnectionAsync user conn@Connection {connId, pqSupport, connChatVersion} confId msg = do cmdId <- withStore' $ \db -> createCommand db user (Just connId) CFAllowConn - dm <- directMessage msg + dm <- encodeConnInfoPQ pqSupport connChatVersion msg withAgent $ \a -> allowConnectionAsync a (aCorrId cmdId) (aConnId conn) confId dm withStore' $ \db -> updateConnectionStatus db conn ConnAccepted -agentAcceptContactAsync :: (MsgEncodingI e, ChatMonad m) => User -> Bool -> InvitationId -> ChatMsgEvent e -> SubscriptionMode -> m (CommandId, ConnId) -agentAcceptContactAsync user enableNtfs invId msg subMode = do +agentAcceptContactAsync :: (MsgEncodingI e, ChatMonad m) => User -> Bool -> InvitationId -> ChatMsgEvent e -> SubscriptionMode -> PQSupport -> VersionChat -> m (CommandId, ConnId) +agentAcceptContactAsync user enableNtfs invId msg subMode pqSup chatV = do cmdId <- withStore' $ \db -> createCommand db user Nothing CFAcceptContact - dm <- directMessage msg - connId <- withAgent $ \a -> acceptContactAsync a (aCorrId cmdId) enableNtfs invId dm subMode + dm <- encodeConnInfoPQ pqSup chatV msg + connId <- withAgent $ \a -> acceptContactAsync a (aCorrId cmdId) enableNtfs invId dm pqSup subMode pure (cmdId, connId) deleteAgentConnectionAsync :: ChatMonad m => User -> ConnId -> m () -deleteAgentConnectionAsync user acId = - withAgent (`deleteConnectionAsync` acId) `catchChatError` (toView . CRChatError (Just user)) +deleteAgentConnectionAsync user acId = deleteAgentConnectionAsync' user acId False + +deleteAgentConnectionAsync' :: ChatMonad m => User -> ConnId -> Bool -> m () +deleteAgentConnectionAsync' user acId waitDelivery = do + withAgent (\a -> deleteConnectionAsync a waitDelivery acId) `catchChatError` (toView . CRChatError (Just user)) deleteAgentConnectionsAsync :: ChatMonad m => User -> [ConnId] -> m () -deleteAgentConnectionsAsync _ [] = pure () -deleteAgentConnectionsAsync user acIds = - withAgent (`deleteConnectionsAsync` acIds) `catchChatError` (toView . CRChatError (Just user)) +deleteAgentConnectionsAsync user acIds = deleteAgentConnectionsAsync' user acIds False + +deleteAgentConnectionsAsync' :: ChatMonad m => User -> [ConnId] -> Bool -> m () +deleteAgentConnectionsAsync' _ [] _ = pure () +deleteAgentConnectionsAsync' user acIds waitDelivery = do + withAgent (\a -> deleteConnectionsAsync a waitDelivery acIds) `catchChatError` (toView . CRChatError (Just user)) agentXFTPDeleteRcvFile :: ChatMonad m => RcvFileId -> FileTransferId -> m () agentXFTPDeleteRcvFile aFileId fileId = do withAgent (`xftpDeleteRcvFile` aFileId) withStore' $ \db -> setRcvFTAgentDeleted db fileId +agentXFTPDeleteRcvFiles :: ChatMonad m => [(XFTPRcvFile, FileTransferId)] -> m () +agentXFTPDeleteRcvFiles rcvFiles = do + let rcvFiles' = filter (not . agentRcvFileDeleted . fst) rcvFiles + rfIds = mapMaybe fileIds rcvFiles' + withAgent $ \a -> xftpDeleteRcvFiles a (map fst rfIds) + void . withStoreBatch' $ \db -> map (setRcvFTAgentDeleted db . snd) rfIds + where + fileIds :: (XFTPRcvFile, FileTransferId) -> Maybe (RcvFileId, FileTransferId) + fileIds (XFTPRcvFile {agentRcvFileId = Just (AgentRcvFileId aFileId)}, fileId) = Just (aFileId, fileId) + fileIds _ = Nothing + agentXFTPDeleteSndFileRemote :: ChatMonad m => User -> XFTPSndFile -> FileTransferId -> m () -agentXFTPDeleteSndFileRemote user XFTPSndFile {agentSndFileId = AgentSndFileId aFileId, privateSndFileDescr, agentSndFileDeleted} fileId = - unless agentSndFileDeleted $ - forM_ privateSndFileDescr $ \sfdText -> do - sd <- parseFileDescription sfdText - withAgent $ \a -> xftpDeleteSndFileRemote a (aUserId user) aFileId sd - withStore' $ \db -> setSndFTAgentDeleted db user fileId +agentXFTPDeleteSndFileRemote user xsf fileId = + agentXFTPDeleteSndFilesRemote user [(xsf, fileId)] + +agentXFTPDeleteSndFilesRemote :: forall m. ChatMonad m => User -> [(XFTPSndFile, FileTransferId)] -> m () +agentXFTPDeleteSndFilesRemote user sndFiles = do + (_errs, redirects) <- partitionEithers <$> withStoreBatch' (\db -> map (lookupFileTransferRedirectMeta db user . snd) sndFiles) + let redirects' = mapMaybe mapRedirectMeta $ concat redirects + sndFilesAll = redirects' <> sndFiles + sndFilesAll' = filter (not . agentSndFileDeleted . fst) sndFilesAll + sndFilesAll'' <- catMaybes <$> mapM sndFileDescr sndFilesAll' + let sfs = map (\(XFTPSndFile {agentSndFileId = AgentSndFileId aFileId}, sfd, _) -> (aFileId, sfd)) sndFilesAll'' + withAgent $ \a -> xftpDeleteSndFilesRemote a (aUserId user) sfs + void . withStoreBatch' $ \db -> map (setSndFTAgentDeleted db user . (\(_, _, fId) -> fId)) sndFilesAll'' + where + mapRedirectMeta :: FileTransferMeta -> Maybe (XFTPSndFile, FileTransferId) + mapRedirectMeta FileTransferMeta {fileId = fileId, xftpSndFile = Just sndFileRedirect} = Just (sndFileRedirect, fileId) + mapRedirectMeta _ = Nothing + sndFileDescr :: (XFTPSndFile, FileTransferId) -> m (Maybe (XFTPSndFile, ValidFileDescription 'FSender, FileTransferId)) + sndFileDescr (xsf@XFTPSndFile {privateSndFileDescr}, fileId) = + join <$> forM privateSndFileDescr parseSndDescr + where + parseSndDescr sfdText = + tryChatError (parseFileDescription sfdText) >>= \case + Left _ -> pure Nothing + Right sd -> pure $ Just (xsf, sd, fileId) userProfileToSend :: User -> Maybe Profile -> Maybe Contact -> Bool -> Profile userProfileToSend user@User {profile = p} incognitoProfile ct inGroup = do @@ -6374,10 +6723,11 @@ waitChatStartedAndActivated = do activated <- readTVar chatActivated unless (isJust started && activated) retry -chatVersionRange :: ChatMonad' m => m VersionRange +chatVersionRange :: ChatMonad' m => m (PQSupport -> VersionRangeChat) chatVersionRange = do ChatConfig {chatVRange} <- asks config pure chatVRange +{-# INLINE chatVersionRange #-} chatCommandP :: Parser ChatCommand chatCommandP = @@ -6421,6 +6771,9 @@ chatCommandP = "/remote_hosts_folder " *> (SetRemoteHostsFolder <$> filePath), "/_files_encrypt " *> (APISetEncryptLocalFiles <$> onOffP), "/contact_merge " *> (SetContactMergeEnabled <$> onOffP), + "/_pq @" *> (APISetContactPQ <$> A.decimal <* A.space <*> (PQEncryption <$> onOffP)), + "/pq @" *> (SetContactPQ <$> displayName <* A.space <*> (PQEncryption <$> onOffP)), + "/pq " *> (APISetPQEncryption . PQSupport <$> onOffP), "/_db export " *> (APIExportArchive <$> jsonP), "/db export" $> ExportArchive, "/_db import " *> (APIImportArchive <$> jsonP), @@ -6429,6 +6782,9 @@ chatCommandP = "/db encrypt " *> (APIStorageEncryption . dbEncryptionConfig "" <$> dbKeyP), "/db key " *> (APIStorageEncryption <$> (dbEncryptionConfig <$> dbKeyP <* A.space <*> dbKeyP)), "/db decrypt " *> (APIStorageEncryption . (`dbEncryptionConfig` "") <$> dbKeyP), + "/db test key " *> (TestStorageEncryption <$> dbKeyP), + "/_save app settings" *> (APISaveAppSettings <$> jsonP), + "/_get app settings" *> (APIGetAppSettings <$> optional (A.space *> jsonP)), "/sql chat " *> (ExecChatStoreSQL <$> textP), "/sql agent " *> (ExecAgentStoreSQL <$> textP), "/sql slow" $> SlowSQLQueries, @@ -6486,6 +6842,7 @@ chatCommandP = "/_server test " *> (APITestProtoServer <$> A.decimal <* A.space <*> strP), "/smp test " *> (TestProtoServer . AProtoServerWithAuth SPSMP <$> strP), "/xftp test " *> (TestProtoServer . AProtoServerWithAuth SPXFTP <$> strP), + "/ntf test " *> (TestProtoServer . AProtoServerWithAuth SPNTF <$> strP), "/_servers " *> (APISetUserProtoServers <$> A.decimal <* A.space <*> srvCfgP), "/smp " *> (SetUserProtoServers . APSC SPSMP . ProtoServersConfig . map toServerCfg <$> protocolServersP), "/smp default" $> SetUserProtoServers (APSC SPSMP $ ProtoServersConfig []), @@ -6666,6 +7023,9 @@ chatCommandP = "/list remote ctrls" $> ListRemoteCtrls, "/stop remote ctrl" $> StopRemoteCtrl, "/delete remote ctrl " *> (DeleteRemoteCtrl <$> A.decimal), + "/_upload " *> (APIUploadStandaloneFile <$> A.decimal <* A.space <*> cryptoFileP), + "/_download info " *> (APIStandaloneFileInfo <$> strP), + "/_download " *> (APIDownloadStandaloneFile <$> A.decimal <* A.space <*> strP_ <*> cryptoFileP), ("/quit" <|> "/q" <|> "/exit") $> QuitChat, ("/version" <|> "/v") $> ShowVersion, "/debug locks" $> DebugLocks, @@ -6845,3 +7205,29 @@ mkValidName = reverse . dropWhile isSpace . fst3 . foldl' addChar ("", '\NUL', 0 | isPunctuation prev = validFirstChar || isSpace c || (punct < 3 && isPunctuation c) | otherwise = validFirstChar || isSpace c || isMark c || isPunctuation c validFirstChar = isLetter c || isNumber c || isSymbol c + +xftpSndFileTransfer_ :: ChatMonad m => User -> CryptoFile -> Integer -> Int -> Maybe ContactOrGroup -> m (FileInvitation, CIFile 'MDSnd, FileTransferMeta) +xftpSndFileTransfer_ user file@(CryptoFile filePath cfArgs) fileSize n contactOrGroup_ = do + let fileName = takeFileName filePath + fInv = xftpFileInvitation fileName fileSize dummyFileDescr + fsFilePath <- toFSFilePath filePath + let srcFile = CryptoFile fsFilePath cfArgs + aFileId <- withAgent $ \a -> xftpSendFile a (aUserId user) srcFile (roundedFDCount n) + -- TODO CRSndFileStart event for XFTP + chSize <- asks $ fileChunkSize . config + ft@FileTransferMeta {fileId} <- withStore' $ \db -> createSndFileTransferXFTP db user contactOrGroup_ file fInv (AgentSndFileId aFileId) Nothing chSize + let fileSource = Just $ CryptoFile filePath cfArgs + ciFile = CIFile {fileId, fileName, fileSize, fileSource, fileStatus = CIFSSndStored, fileProtocol = FPXFTP} + pure (fInv, ciFile, ft) + +xftpSndFileRedirect :: ChatMonad m => User -> FileTransferId -> ValidFileDescription 'FRecipient -> m FileTransferMeta +xftpSndFileRedirect user ftId vfd = do + let fileName = "redirect.yaml" + file = CryptoFile fileName Nothing + fInv = xftpFileInvitation fileName (fromIntegral $ B.length $ strEncode vfd) dummyFileDescr + aFileId <- withAgent $ \a -> xftpSendDescription a (aUserId user) vfd (roundedFDCount 1) + chSize <- asks $ fileChunkSize . config + withStore' $ \db -> createSndFileTransferXFTP db user Nothing file fInv (AgentSndFileId aFileId) (Just ftId) chSize + +dummyFileDescr :: FileDescr +dummyFileDescr = FileDescr {fileDescrText = "", fileDescrPartNo = 0, fileDescrComplete = False} diff --git a/src/Simplex/Chat/AppSettings.hs b/src/Simplex/Chat/AppSettings.hs new file mode 100644 index 0000000000..572ce0c67b --- /dev/null +++ b/src/Simplex/Chat/AppSettings.hs @@ -0,0 +1,190 @@ +{-# LANGUAGE NamedFieldPuns #-} +{-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE StrictData #-} +{-# LANGUAGE TemplateHaskell #-} + +module Simplex.Chat.AppSettings where + +import Control.Applicative ((<|>)) +import Data.Aeson (FromJSON (..), (.:?)) +import qualified Data.Aeson as J +import qualified Data.Aeson.TH as JQ +import Data.Maybe (fromMaybe) +import Data.Text (Text) +import Simplex.Messaging.Client (NetworkConfig, defaultNetworkConfig) +import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON) +import Simplex.Messaging.Util (catchAll_) + +data AppPlatform = APIOS | APAndroid | APDesktop deriving (Show) + +data NotificationMode = NMOff | NMPeriodic | NMInstant deriving (Show) + +data NotificationPreviewMode = NPMHidden | NPMContact | NPMMessage deriving (Show) + +data LockScreenCalls = LSCDisable | LSCShow | LSCAccept deriving (Show) + +data AppSettings = AppSettings + { appPlatform :: Maybe AppPlatform, + networkConfig :: Maybe NetworkConfig, + privacyEncryptLocalFiles :: Maybe Bool, + privacyAcceptImages :: Maybe Bool, + privacyLinkPreviews :: Maybe Bool, + privacyShowChatPreviews :: Maybe Bool, + privacySaveLastDraft :: Maybe Bool, + privacyProtectScreen :: Maybe Bool, + notificationMode :: Maybe NotificationMode, + notificationPreviewMode :: Maybe NotificationPreviewMode, + webrtcPolicyRelay :: Maybe Bool, + webrtcICEServers :: Maybe [Text], + confirmRemoteSessions :: Maybe Bool, + connectRemoteViaMulticast :: Maybe Bool, + connectRemoteViaMulticastAuto :: Maybe Bool, + developerTools :: Maybe Bool, + confirmDBUpgrades :: Maybe Bool, + androidCallOnLockScreen :: Maybe LockScreenCalls, + iosCallKitEnabled :: Maybe Bool, + iosCallKitCallsInRecents :: Maybe Bool + } + deriving (Show) + +defaultAppSettings :: AppSettings +defaultAppSettings = + AppSettings + { appPlatform = Nothing, + networkConfig = Just defaultNetworkConfig, + privacyEncryptLocalFiles = Just True, + privacyAcceptImages = Just True, + privacyLinkPreviews = Just True, + privacyShowChatPreviews = Just True, + privacySaveLastDraft = Just True, + privacyProtectScreen = Just False, + notificationMode = Just NMInstant, + notificationPreviewMode = Just NPMMessage, + webrtcPolicyRelay = Just True, + webrtcICEServers = Just [], + confirmRemoteSessions = Just False, + connectRemoteViaMulticast = Just True, + connectRemoteViaMulticastAuto = Just True, + developerTools = Just False, + confirmDBUpgrades = Just False, + androidCallOnLockScreen = Just LSCShow, + iosCallKitEnabled = Just True, + iosCallKitCallsInRecents = Just False + } + +defaultParseAppSettings :: AppSettings +defaultParseAppSettings = + AppSettings + { appPlatform = Nothing, + networkConfig = Nothing, + privacyEncryptLocalFiles = Nothing, + privacyAcceptImages = Nothing, + privacyLinkPreviews = Nothing, + privacyShowChatPreviews = Nothing, + privacySaveLastDraft = Nothing, + privacyProtectScreen = Nothing, + notificationMode = Nothing, + notificationPreviewMode = Nothing, + webrtcPolicyRelay = Nothing, + webrtcICEServers = Nothing, + confirmRemoteSessions = Nothing, + connectRemoteViaMulticast = Nothing, + connectRemoteViaMulticastAuto = Nothing, + developerTools = Nothing, + confirmDBUpgrades = Nothing, + androidCallOnLockScreen = Nothing, + iosCallKitEnabled = Nothing, + iosCallKitCallsInRecents = Nothing + } + +combineAppSettings :: AppSettings -> AppSettings -> AppSettings +combineAppSettings platformDefaults storedSettings = + AppSettings + { appPlatform = p appPlatform, + networkConfig = p networkConfig, + privacyEncryptLocalFiles = p privacyEncryptLocalFiles, + privacyAcceptImages = p privacyAcceptImages, + privacyLinkPreviews = p privacyLinkPreviews, + privacyShowChatPreviews = p privacyShowChatPreviews, + privacySaveLastDraft = p privacySaveLastDraft, + privacyProtectScreen = p privacyProtectScreen, + notificationMode = p notificationMode, + notificationPreviewMode = p notificationPreviewMode, + webrtcPolicyRelay = p webrtcPolicyRelay, + webrtcICEServers = p webrtcICEServers, + confirmRemoteSessions = p confirmRemoteSessions, + connectRemoteViaMulticast = p connectRemoteViaMulticast, + connectRemoteViaMulticastAuto = p connectRemoteViaMulticastAuto, + developerTools = p developerTools, + confirmDBUpgrades = p confirmDBUpgrades, + iosCallKitEnabled = p iosCallKitEnabled, + iosCallKitCallsInRecents = p iosCallKitCallsInRecents, + androidCallOnLockScreen = p androidCallOnLockScreen + } + where + p :: (AppSettings -> Maybe a) -> Maybe a + p sel = sel storedSettings <|> sel platformDefaults <|> sel defaultAppSettings + +$(JQ.deriveJSON (enumJSON $ dropPrefix "AP") ''AppPlatform) + +$(JQ.deriveJSON (enumJSON $ dropPrefix "NM") ''NotificationMode) + +$(JQ.deriveJSON (enumJSON $ dropPrefix "NPM") ''NotificationPreviewMode) + +$(JQ.deriveJSON (enumJSON $ dropPrefix "LSC") ''LockScreenCalls) + +$(JQ.deriveToJSON defaultJSON ''AppSettings) + +instance FromJSON AppSettings where + parseJSON (J.Object v) = do + appPlatform <- p "appPlatform" + networkConfig <- p "networkConfig" + privacyEncryptLocalFiles <- p "privacyEncryptLocalFiles" + privacyAcceptImages <- p "privacyAcceptImages" + privacyLinkPreviews <- p "privacyLinkPreviews" + privacyShowChatPreviews <- p "privacyShowChatPreviews" + privacySaveLastDraft <- p "privacySaveLastDraft" + privacyProtectScreen <- p "privacyProtectScreen" + notificationMode <- p "notificationMode" + notificationPreviewMode <- p "notificationPreviewMode" + webrtcPolicyRelay <- p "webrtcPolicyRelay" + webrtcICEServers <- p "webrtcICEServers" + confirmRemoteSessions <- p "confirmRemoteSessions" + connectRemoteViaMulticast <- p "connectRemoteViaMulticast" + connectRemoteViaMulticastAuto <- p "connectRemoteViaMulticastAuto" + developerTools <- p "developerTools" + confirmDBUpgrades <- p "confirmDBUpgrades" + iosCallKitEnabled <- p "iosCallKitEnabled" + iosCallKitCallsInRecents <- p "iosCallKitCallsInRecents" + androidCallOnLockScreen <- p "androidCallOnLockScreen" + pure + AppSettings + { appPlatform, + networkConfig, + privacyEncryptLocalFiles, + privacyAcceptImages, + privacyLinkPreviews, + privacyShowChatPreviews, + privacySaveLastDraft, + privacyProtectScreen, + notificationMode, + notificationPreviewMode, + webrtcPolicyRelay, + webrtcICEServers, + confirmRemoteSessions, + connectRemoteViaMulticast, + connectRemoteViaMulticastAuto, + developerTools, + confirmDBUpgrades, + iosCallKitEnabled, + iosCallKitCallsInRecents, + androidCallOnLockScreen + } + where + p key = v .:? key <|> pure Nothing + parseJSON _ = pure defaultParseAppSettings + +readAppSettings :: FilePath -> Maybe AppSettings -> IO AppSettings +readAppSettings f platformDefaults = + combineAppSettings (fromMaybe defaultAppSettings platformDefaults) . fromMaybe defaultParseAppSettings + <$> (J.decodeFileStrict f `catchAll_` pure Nothing) diff --git a/src/Simplex/Chat/Archive.hs b/src/Simplex/Chat/Archive.hs index d386b48d40..4644299598 100644 --- a/src/Simplex/Chat/Archive.hs +++ b/src/Simplex/Chat/Archive.hs @@ -9,6 +9,7 @@ module Simplex.Chat.Archive importArchive, deleteStorage, sqlCipherExport, + sqlCipherTestKey, archiveFilesFolder, ) where @@ -20,6 +21,7 @@ import Control.Monad.Reader import qualified Data.ByteArray as BA import Data.Functor (($>)) import Data.Maybe (fromMaybe) +import Data.Text (Text) import qualified Data.Text as T import qualified Database.SQLite3 as SQL import Simplex.Chat.Controller @@ -147,19 +149,8 @@ sqlCipherExport DBEncryptionConfig {currentKey = DBEncryptionKey key, newKey = D atomically $ writeTVar dbKey $ storeKey key' (fromMaybe False keepKey) export f = do withDB f (`SQL.exec` exportSQL) DBErrorExport - withDB (exported f) (`SQL.exec` testSQL) DBErrorOpen + withDB (exported f) (`SQL.exec` testSQL key') DBErrorOpen where - withDB f' a err = - liftIO (bracket (SQL.open $ T.pack f') SQL.close a $> Nothing) - `catch` checkSQLError - `catch` (\(e :: SomeException) -> sqliteError' e) - >>= mapM_ (throwDBError . err) - where - checkSQLError e = case SQL.sqlError e of - SQL.ErrorNotADatabase -> pure $ Just SQLiteErrorNotADatabase - _ -> sqliteError' e - sqliteError' :: Show e => e -> m (Maybe SQLiteError) - sqliteError' = pure . Just . SQLiteError . show exportSQL = T.unlines $ keySQL key @@ -167,14 +158,38 @@ sqlCipherExport DBEncryptionConfig {currentKey = DBEncryptionKey key, newKey = D "SELECT sqlcipher_export('exported');", "DETACH DATABASE exported;" ] - testSQL = - T.unlines $ - keySQL key' - <> [ "PRAGMA foreign_keys = ON;", - "PRAGMA secure_delete = ON;", - "SELECT count(*) FROM sqlite_master;" - ] - keySQL k = ["PRAGMA key = " <> keyString k <> ";" | not (BA.null k)] + +withDB :: forall a m. ChatMonad m => FilePath -> (SQL.Database -> IO a) -> (SQLiteError -> DatabaseError) -> m () +withDB f' a err = + liftIO (bracket (SQL.open $ T.pack f') SQL.close a $> Nothing) + `catch` checkSQLError + `catch` (\(e :: SomeException) -> sqliteError' e) + >>= mapM_ (throwDBError . err) + where + checkSQLError e = case SQL.sqlError e of + SQL.ErrorNotADatabase -> pure $ Just SQLiteErrorNotADatabase + _ -> sqliteError' e + sqliteError' :: Show e => e -> m (Maybe SQLiteError) + sqliteError' = pure . Just . SQLiteError . show + +testSQL :: BA.ScrubbedBytes -> Text +testSQL k = + T.unlines $ + keySQL k + <> [ "PRAGMA foreign_keys = ON;", + "PRAGMA secure_delete = ON;", + "SELECT count(*) FROM sqlite_master;" + ] + +keySQL :: BA.ScrubbedBytes -> [Text] +keySQL k = ["PRAGMA key = " <> keyString k <> ";" | not (BA.null k)] + +sqlCipherTestKey :: forall m. ChatMonad m => DBEncryptionKey -> m () +sqlCipherTestKey (DBEncryptionKey key) = do + fs <- storageFiles + testKey `withDBs` fs + where + testKey f = withDB f (`SQL.exec` testSQL key) DBErrorOpen withDBs :: Monad m => (FilePath -> m b) -> StorageFiles -> m b action `withDBs` StorageFiles {chatStore, agentStore} = action (dbFilePath chatStore) >> action (dbFilePath agentStore) diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 36dbae8e47..4ca9da094f 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -46,9 +46,12 @@ import Data.Time (NominalDiffTime, UTCTime) import Data.Time.Clock.System (systemToUTCTime) import Data.Version (showVersion) import Data.Word (Word16) +import Database.SQLite.Simple (SQLError) +import qualified Database.SQLite.Simple as SQL import Language.Haskell.TH (Exp, Q, runIO) import Numeric.Natural import qualified Paths_simplex_chat as SC +import Simplex.Chat.AppSettings import Simplex.Chat.Call import Simplex.Chat.Markdown (MarkdownList) import Simplex.Chat.Messages @@ -59,6 +62,7 @@ import Simplex.Chat.Remote.Types import Simplex.Chat.Store (AutoAccept, StoreError (..), UserContactLink, UserMsgReceiptSettings) import Simplex.Chat.Types import Simplex.Chat.Types.Preferences +import Simplex.FileTransfer.Description (FileDescriptionURI) import Simplex.Messaging.Agent (AgentClient, SubscriptionsInfo) import Simplex.Messaging.Agent.Client (AgentLocks, AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure) import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig) @@ -70,6 +74,7 @@ import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..)) import qualified Simplex.Messaging.Crypto.File as CF +import Simplex.Messaging.Crypto.Ratchet (PQEncryption, PQSupport (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON) @@ -77,8 +82,7 @@ import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), Cor import Simplex.Messaging.TMap (TMap) import Simplex.Messaging.Transport (TLS, simplexMQVersion) import Simplex.Messaging.Transport.Client (TransportHost) -import Simplex.Messaging.Util (allFinally, catchAllErrors, liftEitherError, tryAllErrors, (<$$>)) -import Simplex.Messaging.Version +import Simplex.Messaging.Util (allFinally, catchAllErrors, liftIOEither, tryAllErrors, (<$$>)) import Simplex.RemoteControl.Client import Simplex.RemoteControl.Invitation (RCSignedInvitation, RCVerifiedInvitation) import Simplex.RemoteControl.Types @@ -120,7 +124,7 @@ coreVersionInfo simplexmqCommit = data ChatConfig = ChatConfig { agentConfig :: AgentConfig, - chatVRange :: VersionRange, + chatVRange :: PQSupport -> VersionRangeChat, confirmMigrations :: MigrationConfirmation, defaultServers :: DefaultAgentServers, tbqSize :: Natural, @@ -204,7 +208,8 @@ data ChatController = ChatController encryptLocalFiles :: TVar Bool, tempDirectory :: TVar (Maybe FilePath), logFilePath :: Maybe FilePath, - contactMergeEnabled :: TVar Bool + contactMergeEnabled :: TVar Bool, + pqExperimentalEnabled :: TVar PQSupport -- TODO v5.7 remove } data HelpSection = HSMain | HSFiles | HSGroups | HSContacts | HSMyAddress | HSIncognito | HSMarkdown | HSMessages | HSRemote | HSSettings | HSDatabase @@ -241,11 +246,17 @@ data ChatCommand | SetRemoteHostsFolder FilePath | APISetEncryptLocalFiles Bool | SetContactMergeEnabled Bool + | APISetPQEncryption PQSupport + | APISetContactPQ ContactId PQEncryption + | SetContactPQ ContactName PQEncryption | APIExportArchive ArchiveConfig | ExportArchive | APIImportArchive ArchiveConfig + | APISaveAppSettings AppSettings + | APIGetAppSettings (Maybe AppSettings) | APIDeleteStorage | APIStorageEncryption DBEncryptionConfig + | TestStorageEncryption DBEncryptionKey | ExecChatStoreSQL Text | ExecAgentStoreSQL Text | SlowSQLQueries @@ -448,6 +459,9 @@ data ChatCommand | ListRemoteCtrls | StopRemoteCtrl -- Stop listening for announcements or terminate an active session | DeleteRemoteCtrl RemoteCtrlId -- Remove all local data associated with a remote controller session + | APIUploadStandaloneFile UserId CryptoFile + | APIDownloadStandaloneFile UserId FileDescriptionURI CryptoFile + | APIStandaloneFileInfo FileDescriptionURI | QuitChat | ShowVersion | DebugLocks @@ -587,21 +601,27 @@ data ChatResponse | CRRcvFileAccepted {user :: User, chatItem :: AChatItem} | CRRcvFileAcceptedSndCancelled {user :: User, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileDescrNotReady {user :: User, chatItem :: AChatItem} - | CRRcvFileStart {user :: User, chatItem :: AChatItem} - | CRRcvFileProgressXFTP {user :: User, chatItem :: AChatItem, receivedSize :: Int64, totalSize :: Int64} + | CRStandaloneFileInfo {fileMeta :: Maybe J.Value} + | CRRcvStandaloneFileCreated {user :: User, rcvFileTransfer :: RcvFileTransfer} -- returned by _download + | CRRcvFileStart {user :: User, chatItem :: AChatItem} -- sent by chats + | CRRcvFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, receivedSize :: Int64, totalSize :: Int64, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileComplete {user :: User, chatItem :: AChatItem} - | CRRcvFileCancelled {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer} + | CRRcvStandaloneFileComplete {user :: User, targetPath :: FilePath, rcvFileTransfer :: RcvFileTransfer} + | CRRcvFileCancelled {user :: User, chatItem_ :: Maybe AChatItem, rcvFileTransfer :: RcvFileTransfer} | CRRcvFileSndCancelled {user :: User, chatItem :: AChatItem, rcvFileTransfer :: RcvFileTransfer} - | CRRcvFileError {user :: User, chatItem :: AChatItem, agentError :: AgentErrorType} + | CRRcvFileError {user :: User, chatItem_ :: Maybe AChatItem, agentError :: AgentErrorType, rcvFileTransfer :: RcvFileTransfer} | CRSndFileStart {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} | CRSndFileComplete {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} - | CRSndFileRcvCancelled {user :: User, chatItem :: AChatItem, sndFileTransfer :: SndFileTransfer} - | CRSndFileCancelled {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta, sndFileTransfers :: [SndFileTransfer]} - | CRSndFileStartXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta} - | CRSndFileProgressXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta, sentSize :: Int64, totalSize :: Int64} + | CRSndFileRcvCancelled {user :: User, chatItem_ :: Maybe AChatItem, sndFileTransfer :: SndFileTransfer} + | CRSndFileCancelled {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sndFileTransfers :: [SndFileTransfer]} + | CRSndStandaloneFileCreated {user :: User, fileTransferMeta :: FileTransferMeta} -- returned by _upload + | CRSndFileStartXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta} -- not used + | CRSndFileProgressXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, sentSize :: Int64, totalSize :: Int64} + | CRSndFileRedirectStartXFTP {user :: User, fileTransferMeta :: FileTransferMeta, redirectMeta :: FileTransferMeta} | CRSndFileCompleteXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta} - | CRSndFileCancelledXFTP {user :: User, chatItem :: AChatItem, fileTransferMeta :: FileTransferMeta} - | CRSndFileError {user :: User, chatItem :: AChatItem} + | CRSndStandaloneFileComplete {user :: User, fileTransferMeta :: FileTransferMeta, rcvURIs :: [Text]} + | CRSndFileCancelledXFTP {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta} + | CRSndFileError {user :: User, chatItem_ :: Maybe AChatItem, fileTransferMeta :: FileTransferMeta, errorMessage :: Text} | CRUserProfileUpdated {user :: User, fromProfile :: Profile, toProfile :: Profile, updateSummary :: UserProfileUpdateSummary} | CRUserProfileImage {user :: User, profile :: Profile} | CRContactAliasUpdated {user :: User, toContact :: Contact} @@ -667,7 +687,7 @@ data ChatResponse | CRUserContactLinkSubscribed -- TODO delete | CRUserContactLinkSubError {chatError :: ChatError} -- TODO delete | CRNtfTokenStatus {status :: NtfTknStatus} - | CRNtfToken {token :: DeviceToken, status :: NtfTknStatus, ntfMode :: NotificationsMode} + | CRNtfToken {token :: DeviceToken, status :: NtfTknStatus, ntfMode :: NotificationsMode, ntfServer :: NtfServer} | CRNtfMessages {user_ :: Maybe User, connEntity_ :: Maybe ConnectionEntity, msgTs :: Maybe UTCTime, ntfMessages :: [NtfMsgInfo]} | CRNtfMessage {user :: User, connEntity :: ConnectionEntity, ntfMessage :: NtfMsgInfo} | CRContactConnectionDeleted {user :: User, connection :: PendingContactConnection} @@ -685,6 +705,8 @@ data ChatResponse | CRRemoteCtrlSessionCode {remoteCtrl_ :: Maybe RemoteCtrlInfo, sessionCode :: Text} | CRRemoteCtrlConnected {remoteCtrl :: RemoteCtrlInfo} | CRRemoteCtrlStopped {rcsState :: RemoteCtrlSessionState, rcStopReason :: RemoteCtrlStopReason} + | CRContactPQAllowed {user :: User, contact :: Contact, pqEncryption :: PQEncryption} + | CRContactPQEnabled {user :: User, contact :: Contact, pqEnabled :: PQEncryption} | CRSQLResult {rows :: [Text]} | CRSlowSQLQueries {chatQueries :: [SlowSQLQuery], agentQueries :: [SlowSQLQuery]} | CRDebugLocks {chatLockName :: Maybe String, agentLocks :: AgentLocks} @@ -702,6 +724,7 @@ data ChatResponse | CRChatError {user_ :: Maybe User, chatError :: ChatError} | CRChatErrors {user_ :: Maybe User, chatErrors :: [ChatError]} | CRArchiveImported {archiveErrors :: [ArchiveError]} + | CRAppSettings {appSettings :: AppSettings} | CRTimedAction {action :: String, durationMilliseconds :: Int64} deriving (Show) @@ -935,8 +958,8 @@ data NtfMsgInfo = NtfMsgInfo {msgId :: Text, msgTs :: UTCTime} ntfMsgInfo :: SMPMsgMeta -> NtfMsgInfo ntfMsgInfo SMPMsgMeta {msgId, msgTs} = NtfMsgInfo {msgId = decodeLatin1 $ strEncode msgId, msgTs = systemToUTCTime msgTs} -crNtfToken :: (DeviceToken, NtfTknStatus, NotificationsMode) -> ChatResponse -crNtfToken (token, status, ntfMode) = CRNtfToken {token, status, ntfMode} +crNtfToken :: (DeviceToken, NtfTknStatus, NotificationsMode, NtfServer) -> ChatResponse +crNtfToken (token, status, ntfMode, ntfServer) = CRNtfToken {token, status, ntfMode, ntfServer} data SwitchProgress = SwitchProgress { queueDirection :: QueueDirection, @@ -1239,6 +1262,14 @@ mkChatError :: SomeException -> ChatError mkChatError = ChatError . CEException . show {-# INLINE mkChatError #-} +catchStoreError :: ExceptT StoreError IO a -> (StoreError -> ExceptT StoreError IO a) -> ExceptT StoreError IO a +catchStoreError = catchAllErrors mkStoreError +{-# INLINE catchStoreError #-} + +mkStoreError :: SomeException -> StoreError +mkStoreError = SEInternalError . show +{-# INLINE mkStoreError #-} + chatCmdError :: Maybe User -> String -> ChatResponse chatCmdError user = CRChatCmdError user . ChatError . CECommandError @@ -1261,36 +1292,23 @@ withStore' :: ChatMonad m => (DB.Connection -> IO a) -> m a withStore' action = withStore $ liftIO . action withStore :: ChatMonad m => (DB.Connection -> ExceptT StoreError IO a) -> m a -withStore = withStoreCtx Nothing - -withStoreCtx' :: ChatMonad m => Maybe String -> (DB.Connection -> IO a) -> m a -withStoreCtx' ctx_ action = withStoreCtx ctx_ $ liftIO . action - -withStoreCtx :: ChatMonad m => Maybe String -> (DB.Connection -> ExceptT StoreError IO a) -> m a -withStoreCtx ctx_ action = do +withStore action = do ChatController {chatStore} <- ask - liftEitherError ChatErrorStore $ case ctx_ of - Nothing -> withTransaction chatStore (runExceptT . action) `catch` handleInternal "" - -- uncomment to debug store performance - -- Just ctx -> do - -- t1 <- liftIO getCurrentTime - -- putStrLn $ "withStoreCtx start :: " <> show t1 <> " :: " <> ctx - -- r <- withTransactionCtx ctx_ chatStore (runExceptT . action) `E.catch` handleInternal (" (" <> ctx <> ")") - -- t2 <- liftIO getCurrentTime - -- putStrLn $ "withStoreCtx end :: " <> show t2 <> " :: " <> ctx <> " :: duration=" <> show (diffToMilliseconds $ diffUTCTime t2 t1) - -- pure r - Just _ -> withTransaction chatStore (runExceptT . action) `catch` handleInternal "" - where - handleInternal :: String -> SomeException -> IO (Either StoreError a) - handleInternal ctxStr e = pure . Left . SEInternalError $ show e <> ctxStr + liftIOEither $ withTransaction chatStore (runExceptT . withExceptT ChatErrorStore . action) `E.catches` handleDBErrors withStoreBatch :: (ChatMonad' m, Traversable t) => (DB.Connection -> t (IO (Either ChatError a))) -> m (t (Either ChatError a)) withStoreBatch actions = do ChatController {chatStore} <- ask - liftIO $ withTransaction chatStore $ mapM (`E.catch` handleInternal) . actions - where - handleInternal :: E.SomeException -> IO (Either ChatError a) - handleInternal = pure . Left . ChatError . CEInternalError . show + liftIO $ withTransaction chatStore $ mapM (`E.catches` handleDBErrors) . actions + +handleDBErrors :: [E.Handler IO (Either ChatError a)] +handleDBErrors = + [ E.Handler $ \(e :: SQLError) -> + let se = SQL.sqlError e + busy = se == SQL.ErrorBusy || se == SQL.ErrorLocked + in pure . Left . ChatErrorStore $ if busy then SEDBBusyError $ show se else SEDBException $ show e, + E.Handler $ \(E.SomeException e) -> pure . Left . ChatErrorStore . SEDBException $ show e + ] withStoreBatch' :: (ChatMonad' m, Traversable t) => (DB.Connection -> t (IO a)) -> m (t (Either ChatError a)) withStoreBatch' actions = withStoreBatch $ fmap (fmap Right) . actions diff --git a/src/Simplex/Chat/Help.hs b/src/Simplex/Chat/Help.hs index ac93e05533..adb77b9557 100644 --- a/src/Simplex/Chat/Help.hs +++ b/src/Simplex/Chat/Help.hs @@ -185,6 +185,8 @@ contactsHelpInfo = indent <> highlight "/verify @<name> " <> " - clear security code verification", indent <> highlight "/info @<name> " <> " - info about contact connection", indent <> highlight "/switch @<name> " <> " - switch receiving messages to another SMP relay", + indent <> highlight "/pq @<name> on/off " <> " - [BETA] toggle quantum resistant / standard e2e encryption for a contact", + indent <> " " <> " (both have to enable for quantum resistance)", "", green "Contact chat preferences:", indent <> highlight "/set voice @<name> yes/no/always " <> " - allow/prohibit voice messages with the contact", @@ -320,6 +322,7 @@ settingsInfo = map styleMarkdown [ green "Chat settings:", + indent <> highlight "/pq on/off " <> " - [BETA] toggle quantum resistant / standard e2e encryption for the new contacts", indent <> highlight "/network " <> " - show / set network access options", indent <> highlight "/smp " <> " - show / set configured SMP servers", indent <> highlight "/xftp " <> " - show / set configured XFTP servers", diff --git a/src/Simplex/Chat/Markdown.hs b/src/Simplex/Chat/Markdown.hs index 6ee4898e3d..2eabb48166 100644 --- a/src/Simplex/Chat/Markdown.hs +++ b/src/Simplex/Chat/Markdown.hs @@ -30,10 +30,11 @@ import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Simplex.Chat.Types import Simplex.Chat.Types.Util -import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ConnReqScheme (..), ConnReqUriData (..), ConnectionRequestUri (..), SMPQueue (..)) +import Simplex.Messaging.Agent.Protocol (AConnectionRequestUri (..), ConnReqUriData (..), ConnectionRequestUri (..), SMPQueue (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, sumTypeJSON) import Simplex.Messaging.Protocol (ProtocolServer (..)) +import Simplex.Messaging.ServiceScheme (ServiceScheme (..)) import Simplex.Messaging.Util (safeDecodeUtf8) import System.Console.ANSI.Types import qualified Text.Email.Validate as Email @@ -231,10 +232,10 @@ markdownP = mconcat <$> A.many' fragmentP simplexUriFormat :: AConnectionRequestUri -> Format simplexUriFormat = \case ACR _ (CRContactUri crData) -> - let uri = safeDecodeUtf8 . strEncode $ CRContactUri crData {crScheme = CRSSimplex} + let uri = safeDecodeUtf8 . strEncode $ CRContactUri crData {crScheme = SSSimplex} in SimplexLink (linkType' crData) uri $ uriHosts crData ACR _ (CRInvitationUri crData e2e) -> - let uri = safeDecodeUtf8 . strEncode $ CRInvitationUri crData {crScheme = CRSSimplex} e2e + let uri = safeDecodeUtf8 . strEncode $ CRInvitationUri crData {crScheme = SSSimplex} e2e in SimplexLink XLInvitation uri $ uriHosts crData where uriHosts ConnReqUriData {crSmpQueues} = L.map (safeDecodeUtf8 . strEncode) $ sconcat $ L.map (host . qServer) crSmpQueues diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 50b098bb71..4312cfa858 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -360,6 +360,24 @@ mkCIMeta itemId itemContent itemText itemStatus itemSharedMsgId itemDeleted item _ -> False in CIMeta {itemId, itemTs, itemText, itemStatus, itemSharedMsgId, itemDeleted, itemEdited, itemTimed, itemLive, editable, forwardedByMember, createdAt, updatedAt} +dummyMeta :: ChatItemId -> UTCTime -> Text -> CIMeta c 'MDSnd +dummyMeta itemId ts itemText = + CIMeta + { itemId, + itemTs = ts, + itemText, + itemStatus = CISSndNew, + itemSharedMsgId = Nothing, + itemDeleted = Nothing, + itemEdited = False, + itemTimed = Nothing, + itemLive = Nothing, + editable = False, + forwardedByMember = Nothing, + createdAt = ts, + updatedAt = ts + } + data CITimed = CITimed { ttl :: Int, -- seconds deleteAt :: Maybe UTCTime -- this is initially Nothing for received items, the timer starts when they are read diff --git a/src/Simplex/Chat/Messages/CIContent.hs b/src/Simplex/Chat/Messages/CIContent.hs index f0ce2d6274..0e95570b85 100644 --- a/src/Simplex/Chat/Messages/CIContent.hs +++ b/src/Simplex/Chat/Messages/CIContent.hs @@ -6,6 +6,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} {-# LANGUAGE TemplateHaskell #-} @@ -29,6 +30,7 @@ import Simplex.Chat.Types import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Util import Simplex.Messaging.Agent.Protocol (MsgErrorType (..), RatchetSyncState (..), SwitchPhase (..)) +import Simplex.Messaging.Crypto.Ratchet (PQEncryption, pattern PQEncOn, pattern PQEncOff) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fstToLower, singleFieldJSON, sumTypeJSON) import Simplex.Messaging.Util (safeDecodeUtf8, tshow, (<$?>)) @@ -139,13 +141,21 @@ data CIContent (d :: MsgDirection) where CISndModerated :: CIContent 'MDSnd CIRcvModerated :: CIContent 'MDRcv CIRcvBlocked :: CIContent 'MDRcv - CIInvalidJSON :: Text -> CIContent d + CISndDirectE2EEInfo :: E2EInfo -> CIContent 'MDSnd + CIRcvDirectE2EEInfo :: E2EInfo -> CIContent 'MDRcv + CISndGroupE2EEInfo :: E2EInfo -> CIContent 'MDSnd -- when new group is created + CIRcvGroupE2EEInfo :: E2EInfo -> CIContent 'MDRcv -- when enabled with some member + CIInvalidJSON :: Text -> CIContent d -- this is also used for logical database errors, e.g. SEBadChatItem + -- ^ This type is used both in API and in DB, so we use different JSON encodings for the database and for the API -- ! ^ Nested sum types also have to use different encodings for database and API -- ! ^ to avoid breaking cross-platform compatibility, see RcvGroupEvent and SndGroupEvent deriving instance Show (CIContent d) +data E2EInfo = E2EInfo {pqEnabled :: PQEncryption} + deriving (Eq, Show) + ciMsgContent :: CIContent d -> Maybe MsgContent ciMsgContent = \case CISndMsgContent mc -> Just mc @@ -172,7 +182,7 @@ ciRequiresAttention content = case msgDirection @d of CIRcvGroupInvitation {} -> True CIRcvDirectEvent rde -> case rde of RDEContactDeleted -> False - RDEProfileUpdated {} -> True + RDEProfileUpdated {} -> False CIRcvGroupEvent rge -> case rge of RGEMemberAdded {} -> False RGEMemberConnected -> False @@ -195,6 +205,8 @@ ciRequiresAttention content = case msgDirection @d of CIRcvGroupFeatureRejected _ -> True CIRcvModerated -> True CIRcvBlocked -> False + CIRcvDirectE2EEInfo _ -> False + CIRcvGroupE2EEInfo _ -> False CIInvalidJSON _ -> False newtype DBMsgErrorType = DBME MsgErrorType @@ -250,8 +262,28 @@ ciContentToText = \case CISndModerated -> ciModeratedText CIRcvModerated -> ciModeratedText CIRcvBlocked -> "blocked" + CISndDirectE2EEInfo e2eeInfo -> directE2EInfoToText e2eeInfo + CIRcvDirectE2EEInfo e2eeInfo -> directE2EInfoToText e2eeInfo + CISndGroupE2EEInfo e2eeInfo -> groupE2EInfoToText e2eeInfo + CIRcvGroupE2EEInfo e2eeInfo -> groupE2EInfoToText e2eeInfo CIInvalidJSON _ -> "invalid content JSON" +directE2EInfoToText :: E2EInfo -> Text +directE2EInfoToText E2EInfo {pqEnabled} = case pqEnabled of + PQEncOn -> e2eInfoPQText + PQEncOff -> e2eInfoNoPQText + +groupE2EInfoToText :: E2EInfo -> Text +groupE2EInfoToText _e2eeInfo = e2eInfoNoPQText + +e2eInfoNoPQText :: Text +e2eInfoNoPQText = + "This conversation is protected by end-to-end encryption with perfect forward secrecy, repudiation and break-in recovery." + +e2eInfoPQText :: Text +e2eInfoPQText = + "This conversation is protected by quantum resistant end-to-end encryption. It has perfect forward secrecy, repudiation and quantum resistant break-in recovery." + ciGroupInvitationToText :: CIGroupInvitation -> GroupMemberRole -> Text ciGroupInvitationToText CIGroupInvitation {groupProfile = GroupProfile {displayName, fullName}} role = "invitation to join group " <> displayName <> optionalFullName displayName fullName <> " as " <> (decodeLatin1 . strEncode $ role) @@ -295,6 +327,9 @@ rcvConnEventToText = \case SPCompleted -> "changed address for you" RCERatchetSync syncStatus -> ratchetSyncStatusToText syncStatus RCEVerificationCodeReset -> "security code changed" + RCEPqEnabled pqEnc -> case pqEnc of + PQEncOn -> "quantum resistant e2e encryption" + PQEncOff -> "standard end-to-end encryption" ratchetSyncStatusToText :: RatchetSyncState -> Text ratchetSyncStatusToText = \case @@ -312,6 +347,9 @@ sndConnEventToText = \case SPSecured -> "secured new address" <> forMember m <> "..." SPCompleted -> "you changed address" <> forMember m SCERatchetSync syncStatus m -> ratchetSyncStatusToText syncStatus <> forMember m + SCEPqEnabled pqEnc -> case pqEnc of + PQEncOn -> "quantum resistant e2e encryption" + PQEncOff -> "standard end-to-end encryption" where forMember member_ = maybe "" (\GroupMemberRef {profile = Profile {displayName}} -> " for " <> displayName) member_ @@ -382,6 +420,10 @@ data JSONCIContent | JCISndModerated | JCIRcvModerated | JCIRcvBlocked + | JCISndDirectE2EEInfo {e2eeInfo :: E2EInfo} + | JCIRcvDirectE2EEInfo {e2eeInfo :: E2EInfo} + | JCISndGroupE2EEInfo {e2eeInfo :: E2EInfo} + | JCIRcvGroupE2EEInfo {e2eeInfo :: E2EInfo} | JCIInvalidJSON {direction :: MsgDirection, json :: Text} jsonCIContent :: forall d. MsgDirectionI d => CIContent d -> JSONCIContent @@ -412,6 +454,10 @@ jsonCIContent = \case CISndModerated -> JCISndModerated CIRcvModerated -> JCIRcvModerated CIRcvBlocked -> JCIRcvBlocked + CISndDirectE2EEInfo e2eeInfo -> JCISndDirectE2EEInfo e2eeInfo + CIRcvDirectE2EEInfo e2eeInfo -> JCIRcvDirectE2EEInfo e2eeInfo + CISndGroupE2EEInfo e2eeInfo -> JCISndGroupE2EEInfo e2eeInfo + CIRcvGroupE2EEInfo e2eeInfo -> JCIRcvGroupE2EEInfo e2eeInfo CIInvalidJSON json -> JCIInvalidJSON (toMsgDirection $ msgDirection @d) json aciContentJSON :: JSONCIContent -> ACIContent @@ -442,6 +488,10 @@ aciContentJSON = \case JCISndModerated -> ACIContent SMDSnd CISndModerated JCIRcvModerated -> ACIContent SMDRcv CIRcvModerated JCIRcvBlocked -> ACIContent SMDRcv CIRcvBlocked + JCISndDirectE2EEInfo {e2eeInfo} -> ACIContent SMDSnd $ CISndDirectE2EEInfo e2eeInfo + JCIRcvDirectE2EEInfo {e2eeInfo} -> ACIContent SMDRcv $ CIRcvDirectE2EEInfo e2eeInfo + JCISndGroupE2EEInfo {e2eeInfo} -> ACIContent SMDSnd $ CISndGroupE2EEInfo e2eeInfo + JCIRcvGroupE2EEInfo {e2eeInfo} -> ACIContent SMDRcv $ CIRcvGroupE2EEInfo e2eeInfo JCIInvalidJSON dir json -> case fromMsgDirection dir of AMsgDirection d -> ACIContent d $ CIInvalidJSON json @@ -473,6 +523,10 @@ data DBJSONCIContent | DBJCISndModerated | DBJCIRcvModerated | DBJCIRcvBlocked + | DBJCISndDirectE2EEInfo {e2eeInfo :: E2EInfo} + | DBJCIRcvDirectE2EEInfo {e2eeInfo :: E2EInfo} + | DBJCISndGroupE2EEInfo {e2eeInfo :: E2EInfo} + | DBJCIRcvGroupE2EEInfo {e2eeInfo :: E2EInfo} | DBJCIInvalidJSON {direction :: MsgDirection, json :: Text} dbJsonCIContent :: forall d. MsgDirectionI d => CIContent d -> DBJSONCIContent @@ -503,6 +557,10 @@ dbJsonCIContent = \case CISndModerated -> DBJCISndModerated CIRcvModerated -> DBJCIRcvModerated CIRcvBlocked -> DBJCIRcvBlocked + CISndDirectE2EEInfo e2eeInfo -> DBJCISndDirectE2EEInfo e2eeInfo + CIRcvDirectE2EEInfo e2eeInfo -> DBJCIRcvDirectE2EEInfo e2eeInfo + CISndGroupE2EEInfo e2eeInfo -> DBJCISndGroupE2EEInfo e2eeInfo + CIRcvGroupE2EEInfo e2eeInfo -> DBJCIRcvGroupE2EEInfo e2eeInfo CIInvalidJSON json -> DBJCIInvalidJSON (toMsgDirection $ msgDirection @d) json aciContentDBJSON :: DBJSONCIContent -> ACIContent @@ -533,6 +591,10 @@ aciContentDBJSON = \case DBJCISndModerated -> ACIContent SMDSnd CISndModerated DBJCIRcvModerated -> ACIContent SMDRcv CIRcvModerated DBJCIRcvBlocked -> ACIContent SMDRcv CIRcvBlocked + DBJCISndDirectE2EEInfo e2eeInfo -> ACIContent SMDSnd $ CISndDirectE2EEInfo e2eeInfo + DBJCIRcvDirectE2EEInfo e2eeInfo -> ACIContent SMDRcv $ CIRcvDirectE2EEInfo e2eeInfo + DBJCISndGroupE2EEInfo e2eeInfo -> ACIContent SMDSnd $ CISndGroupE2EEInfo e2eeInfo + DBJCIRcvGroupE2EEInfo e2eeInfo -> ACIContent SMDRcv $ CIRcvGroupE2EEInfo e2eeInfo DBJCIInvalidJSON dir json -> case fromMsgDirection dir of AMsgDirection d -> ACIContent d $ CIInvalidJSON json @@ -558,6 +620,8 @@ ciCallInfoText status duration = case status of CISCallEnded -> "ended " <> durationText duration CISCallError -> "error" +$(JQ.deriveJSON defaultJSON ''E2EInfo) + $(JQ.deriveJSON (enumJSON $ dropPrefix "MDE") ''MsgDecryptError) $(JQ.deriveJSON (enumJSON $ dropPrefix "CIGIS") ''CIGroupInvitationStatus) @@ -626,4 +690,8 @@ toCIContentTag ciContent = case ciContent of CISndModerated -> "sndModerated" CIRcvModerated -> "rcvModerated" CIRcvBlocked -> "rcvBlocked" + CISndDirectE2EEInfo _ -> "sndDirectE2EEInfo" + CIRcvDirectE2EEInfo _ -> "rcvDirectE2EEInfo" + CISndGroupE2EEInfo _ -> "sndGroupE2EEInfo" + CIRcvGroupE2EEInfo _ -> "rcvGroupE2EEInfo" CIInvalidJSON _ -> "invalidJSON" diff --git a/src/Simplex/Chat/Messages/CIContent/Events.hs b/src/Simplex/Chat/Messages/CIContent/Events.hs index 05417a2e14..7ce5f73cde 100644 --- a/src/Simplex/Chat/Messages/CIContent/Events.hs +++ b/src/Simplex/Chat/Messages/CIContent/Events.hs @@ -9,6 +9,7 @@ import qualified Data.Aeson.TH as J import Simplex.Chat.Types import Simplex.Messaging.Agent.Protocol (RatchetSyncState (..), SwitchPhase (..)) import Simplex.Messaging.Parsers (dropPrefix, singleFieldJSON, sumTypeJSON) +import Simplex.Messaging.Crypto.Ratchet (PQEncryption) data RcvGroupEvent = RGEMemberAdded {groupMemberId :: GroupMemberId, profile :: Profile} -- CRJoinedGroupMemberConnecting @@ -42,11 +43,13 @@ data RcvConnEvent = RCESwitchQueue {phase :: SwitchPhase} | RCERatchetSync {syncStatus :: RatchetSyncState} | RCEVerificationCodeReset + | RCEPqEnabled {enabled :: PQEncryption} deriving (Show) data SndConnEvent = SCESwitchQueue {phase :: SwitchPhase, member :: Maybe GroupMemberRef} | SCERatchetSync {syncStatus :: RatchetSyncState, member :: Maybe GroupMemberRef} + | SCEPqEnabled {enabled :: PQEncryption} deriving (Show) data RcvDirectEvent diff --git a/src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs b/src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs new file mode 100644 index 0000000000..da8f4d413b --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20240214_redirect_file_id.hs @@ -0,0 +1,22 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20240214_redirect_file_id where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20240214_redirect_file_id :: Query +m20240214_redirect_file_id = + [sql| +ALTER TABLE files ADD COLUMN redirect_file_id INTEGER REFERENCES files ON DELETE CASCADE; + +CREATE INDEX idx_files_redirect_file_id on files(redirect_file_id); +|] + +down_m20240214_redirect_file_id :: Query +down_m20240214_redirect_file_id = + [sql| +DROP INDEX idx_files_redirect_file_id; + +ALTER TABLE files DROP COLUMN redirect_file_id; +|] diff --git a/src/Simplex/Chat/Migrations/M20240222_app_settings.hs b/src/Simplex/Chat/Migrations/M20240222_app_settings.hs new file mode 100644 index 0000000000..e7fda06a2e --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20240222_app_settings.hs @@ -0,0 +1,20 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20240222_app_settings where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20240222_app_settings :: Query +m20240222_app_settings = + [sql| +CREATE TABLE app_settings ( + app_settings TEXT NOT NULL +); +|] + +down_m20240222_app_settings :: Query +down_m20240222_app_settings = + [sql| +DROP TABLE app_settings; +|] diff --git a/src/Simplex/Chat/Migrations/M20240226_users_restrict.hs b/src/Simplex/Chat/Migrations/M20240226_users_restrict.hs new file mode 100644 index 0000000000..a68923142c --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20240226_users_restrict.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20240226_users_restrict where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20240226_users_restrict :: Query +m20240226_users_restrict = + [sql| +PRAGMA writable_schema=1; + +UPDATE sqlite_master +SET sql = replace(sql, 'ON DELETE CASCADE', 'ON DELETE RESTRICT') +WHERE name = 'users' AND type = 'table'; + +PRAGMA writable_schema=0; +|] + +down_m20240226_users_restrict :: Query +down_m20240226_users_restrict = + [sql| +PRAGMA writable_schema=1; + +UPDATE sqlite_master +SET sql = replace(sql, 'ON DELETE RESTRICT', 'ON DELETE CASCADE') +WHERE name = 'users' AND type = 'table'; + +PRAGMA writable_schema=0; +|] diff --git a/src/Simplex/Chat/Migrations/M20240228_pq.hs b/src/Simplex/Chat/Migrations/M20240228_pq.hs new file mode 100644 index 0000000000..c496d33b4b --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20240228_pq.hs @@ -0,0 +1,30 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20240228_pq where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20240228_pq :: Query +m20240228_pq = + [sql| +ALTER TABLE connections ADD COLUMN conn_chat_version INTEGER; +ALTER TABLE connections ADD COLUMN pq_support INTEGER NOT NULL DEFAULT 0; +ALTER TABLE connections ADD COLUMN pq_encryption INTEGER NOT NULL DEFAULT 0; +ALTER TABLE connections ADD COLUMN pq_snd_enabled INTEGER; +ALTER TABLE connections ADD COLUMN pq_rcv_enabled INTEGER; + +ALTER TABLE contact_requests ADD COLUMN pq_support INTEGER NOT NULL DEFAULT 0; +|] + +down_m20240228_pq :: Query +down_m20240228_pq = + [sql| +ALTER TABLE contact_requests DROP COLUMN pq_support; + +ALTER TABLE connections DROP COLUMN conn_chat_version; +ALTER TABLE connections DROP COLUMN pq_support; +ALTER TABLE connections DROP COLUMN pq_encryption; +ALTER TABLE connections DROP COLUMN pq_snd_enabled; +ALTER TABLE connections DROP COLUMN pq_rcv_enabled; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index efed6d168a..19c6ba24d0 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -22,7 +22,7 @@ CREATE TABLE contact_profiles( ); CREATE TABLE users( user_id INTEGER PRIMARY KEY, - contact_id INTEGER NOT NULL UNIQUE REFERENCES contacts ON DELETE CASCADE + contact_id INTEGER NOT NULL UNIQUE REFERENCES contacts ON DELETE RESTRICT DEFERRABLE INITIALLY DEFERRED, local_display_name TEXT NOT NULL UNIQUE, active_user INTEGER NOT NULL DEFAULT 0, @@ -37,7 +37,7 @@ CREATE TABLE users( user_member_profile_updated_at TEXT, -- 1 for active user FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) - ON DELETE CASCADE + ON DELETE RESTRICT ON UPDATE CASCADE DEFERRABLE INITIALLY DEFERRED ); @@ -193,7 +193,8 @@ CREATE TABLE files( protocol TEXT NOT NULL DEFAULT 'smp', file_crypto_key BLOB, file_crypto_nonce BLOB, - note_folder_id INTEGER DEFAULT NULL REFERENCES note_folders ON DELETE CASCADE + note_folder_id INTEGER DEFAULT NULL REFERENCES note_folders ON DELETE CASCADE, + redirect_file_id INTEGER REFERENCES files ON DELETE CASCADE ); CREATE TABLE snd_files( file_id INTEGER NOT NULL REFERENCES files ON DELETE CASCADE, @@ -276,6 +277,11 @@ CREATE TABLE connections( peer_chat_max_version INTEGER NOT NULL DEFAULT 1, to_subscribe INTEGER DEFAULT 0 NOT NULL, contact_conn_initiated INTEGER NOT NULL DEFAULT 0, + conn_chat_version INTEGER, + pq_support INTEGER NOT NULL DEFAULT 0, + pq_encryption INTEGER NOT NULL DEFAULT 0, + pq_snd_enabled INTEGER, + pq_rcv_enabled INTEGER, FOREIGN KEY(snd_file_id, connection_id) REFERENCES snd_files(file_id, connection_id) ON DELETE CASCADE @@ -311,6 +317,7 @@ CREATE TABLE contact_requests( xcontact_id BLOB, 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, FOREIGN KEY(user_id, local_display_name) REFERENCES display_names(user_id, local_display_name) ON UPDATE CASCADE @@ -561,6 +568,7 @@ CREATE TABLE note_folders( favorite INTEGER NOT NULL DEFAULT 0, unread_chat INTEGER NOT NULL DEFAULT 0 ); +CREATE TABLE app_settings(app_settings TEXT NOT NULL); CREATE INDEX contact_profiles_index ON contact_profiles( display_name, full_name @@ -854,3 +862,4 @@ CREATE INDEX idx_chat_items_notes_item_status on chat_items( note_folder_id, item_status ); +CREATE INDEX idx_files_redirect_file_id on files(redirect_file_id); diff --git a/src/Simplex/Chat/Mobile.hs b/src/Simplex/Chat/Mobile.hs index 105dedb32c..5883c6042c 100644 --- a/src/Simplex/Chat/Mobile.hs +++ b/src/Simplex/Chat/Mobile.hs @@ -207,6 +207,7 @@ mobileChatOpts dbFilePrefix = chatCmdLog = CCLNone, chatServerPort = Nothing, optFilesFolder = Nothing, + optTempDirectory = Nothing, showReactions = False, allowInstantFiles = True, autoAcceptFileSize = 0, diff --git a/src/Simplex/Chat/Options.hs b/src/Simplex/Chat/Options.hs index a222e2e77b..871e3358ec 100644 --- a/src/Simplex/Chat/Options.hs +++ b/src/Simplex/Chat/Options.hs @@ -41,6 +41,7 @@ data ChatOpts = ChatOpts chatCmdLog :: ChatCmdLog, chatServerPort :: Maybe String, optFilesFolder :: Maybe FilePath, + optTempDirectory :: Maybe FilePath, showReactions :: Bool, allowInstantFiles :: Bool, autoAcceptFileSize :: Integer, @@ -258,6 +259,13 @@ chatOptsP appDir defaultDbFileName = do <> metavar "FOLDER" <> help "Folder to use for sent and received files" ) + optTempDirectory <- + optional $ + strOption + ( long "temp-folder" + <> metavar "FOLDER" + <> help "Folder for temporary encrypted files (default: system temp directory)" + ) showReactions <- switch ( long "reactions" @@ -304,6 +312,7 @@ chatOptsP appDir defaultDbFileName = do chatCmdLog, chatServerPort, optFilesFolder, + optTempDirectory, showReactions, allowInstantFiles, autoAcceptFileSize, diff --git a/src/Simplex/Chat/Protocol.hs b/src/Simplex/Chat/Protocol.hs index c4423bfe6a..e2810dafa9 100644 --- a/src/Simplex/Chat/Protocol.hs +++ b/src/Simplex/Chat/Protocol.hs @@ -7,6 +7,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RankNTypes #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE StandaloneDeriving #-} @@ -30,6 +31,7 @@ import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B import Data.ByteString.Internal (c2w, w2c) import qualified Data.ByteString.Lazy.Char8 as LB +import qualified Data.List.NonEmpty as L import Data.Maybe (fromMaybe) import Data.String import Data.Text (Text) @@ -44,49 +46,71 @@ import Database.SQLite.Simple.ToField (ToField (..)) import Simplex.Chat.Call import Simplex.Chat.Types import Simplex.Chat.Types.Util +import Simplex.Messaging.Agent.Protocol (VersionSMPA, pqdrSMPAgentVersion) +import Simplex.Messaging.Compression (compress1, decompressBatch) +import Simplex.Messaging.Crypto.Ratchet (PQSupport (..), pattern PQSupportOn, pattern PQSupportOff) import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, fromTextField_, fstToLower, parseAll, sumTypeJSON, taggedObjectJSON) +import Simplex.Messaging.Protocol (MsgBody) import Simplex.Messaging.Util (eitherToMaybe, safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Version hiding (version) +-- Chat version history: +-- 1 - support chat versions in connections (9/1/2023) +-- 2 - create contacts for group members only via x.grp.direct.inv (9/16/2023) +-- 3 - faster joining via group links without creating contact (10/30/2023) +-- 4 - group message forwarding (11/18/2023) +-- 5 - batch sending messages (12/23/2023) +-- 6 - send group welcome message after history (12/29/2023) +-- 7 - update member profiles (1/15/2024) + -- This should not be used directly in code, instead use `maxVersion chatVRange` from ChatConfig. -- This indirection is needed for backward/forward compatibility testing. -- Testing with real app versions is still needed, as tests use the current code with different version ranges, not the old code. -currentChatVersion :: Version -currentChatVersion = 7 +currentChatVersion :: VersionChat +currentChatVersion = VersionChat 7 -- This should not be used directly in code, instead use `chatVRange` from ChatConfig (see comment above) -supportedChatVRange :: VersionRange -supportedChatVRange = mkVersionRange 1 currentChatVersion +-- TODO remove parameterization in 5.7 +supportedChatVRange :: PQSupport -> VersionRangeChat +supportedChatVRange pq = mkVersionRange initialChatVersion $ case pq of + PQSupportOn -> pqEncryptionCompressionVersion + PQSupportOff -> currentChatVersion +{-# INLINE supportedChatVRange #-} --- version range that supports skipping establishing direct connections in a group -groupNoDirectVRange :: VersionRange -groupNoDirectVRange = mkVersionRange 2 currentChatVersion - --- version range that supports establishing direct connection via x.grp.direct.inv with a group member -xGrpDirectInvVRange :: VersionRange -xGrpDirectInvVRange = mkVersionRange 2 currentChatVersion +-- version range that supports skipping establishing direct connections in a group and establishing direct connection via x.grp.direct.inv +groupDirectInvVersion :: VersionChat +groupDirectInvVersion = VersionChat 2 -- version range that supports joining group via group link without creating direct contact -groupLinkNoContactVRange :: VersionRange -groupLinkNoContactVRange = mkVersionRange 3 currentChatVersion +groupFastLinkJoinVersion :: VersionChat +groupFastLinkJoinVersion = VersionChat 3 -- version range that supports group forwarding -groupForwardVRange :: VersionRange -groupForwardVRange = mkVersionRange 4 currentChatVersion +groupForwardVersion :: VersionChat +groupForwardVersion = VersionChat 4 -- version range that supports batch sending in groups -batchSendVRange :: VersionRange -batchSendVRange = mkVersionRange 5 currentChatVersion +batchSendVersion :: VersionChat +batchSendVersion = VersionChat 5 -- version range that supports sending group welcome message in group history -groupHistoryIncludeWelcomeVRange :: VersionRange -groupHistoryIncludeWelcomeVRange = mkVersionRange 6 currentChatVersion +groupHistoryIncludeWelcomeVersion :: VersionChat +groupHistoryIncludeWelcomeVersion = VersionChat 6 -- version range that supports sending member profile updates to groups -memberProfileUpdateVRange :: VersionRange -memberProfileUpdateVRange = mkVersionRange 7 currentChatVersion +memberProfileUpdateVersion :: VersionChat +memberProfileUpdateVersion = VersionChat 7 + +-- version range that supports compressing messages and PQ e2e encryption +pqEncryptionCompressionVersion :: VersionChat +pqEncryptionCompressionVersion = VersionChat 8 + +agentToChatVersion :: VersionSMPA -> VersionChat +agentToChatVersion v + | v < pqdrSMPAgentVersion = initialChatVersion + | otherwise = pqEncryptionCompressionVersion data ConnectionEntity = RcvDirectMsgConnection {entityConnection :: Connection, contact :: Maybe Contact} @@ -217,7 +241,7 @@ instance ToJSON LinkContent where $(JQ.deriveJSON defaultJSON ''LinkPreview) data ChatMessage e = ChatMessage - { chatVRange :: VersionRange, + { chatVRange :: VersionRangeChat, msgId :: Maybe SharedMsgId, chatMsgEvent :: ChatMsgEvent e } @@ -507,17 +531,29 @@ $(JQ.deriveJSON defaultJSON ''QuotedMsg) -- this limit reserves space for metadata in forwarded messages -- 15780 (limit used for fileChunkSize) - 161 (x.grp.msg.forward overhead) = 15619, round to 15610 -maxChatMsgSize :: Int -maxChatMsgSize = 15610 +maxEncodedMsgLength :: Int +maxEncodedMsgLength = 15610 + +-- maxEncodedMsgLength - 2222, see e2eEncUserMsgLength in agent +maxCompressedMsgLength :: Int +maxCompressedMsgLength = 13388 + +-- maxEncodedMsgLength - delta between MSG and INFO + 100 (returned for forward overhead) +-- delta between MSG and INFO = e2eEncUserMsgLength (no PQ) - e2eEncConnInfoLength (no PQ) = 1008 +maxEncodedInfoLength :: Int +maxEncodedInfoLength = 14702 + +maxCompressedInfoLength :: Int +maxCompressedInfoLength = 10976 -- maxEncodedInfoLength - 3726, see e2eEncConnInfoLength in agent data EncodedChatMessage = ECMEncoded ByteString | ECMLarge -encodeChatMessage :: MsgEncodingI e => ChatMessage e -> EncodedChatMessage -encodeChatMessage msg = do +encodeChatMessage :: MsgEncodingI e => Int -> ChatMessage e -> EncodedChatMessage +encodeChatMessage maxSize msg = do case chatToAppMessage msg of AMJson m -> do let body = LB.toStrict $ J.encode m - if B.length body > maxChatMsgSize + if B.length body > maxSize then ECMLarge else ECMEncoded body AMBinary m -> ECMEncoded $ strEncode m @@ -529,10 +565,23 @@ parseChatMessages s = case B.head s of '[' -> case J.eitherDecodeStrict' s of Right v -> map parseItem v Left e -> [Left e] + 'X' -> decodeCompressed (B.drop 1 s) _ -> [ACMsg SBinary <$> (appBinaryToCM =<< strDecode s)] where parseItem :: J.Value -> Either String AChatMessage parseItem v = ACMsg SJson <$> JT.parseEither parseJSON v + decodeCompressed :: ByteString -> [Either String AChatMessage] + decodeCompressed s' = case smpDecode s' of + Left e -> [Left e] + -- TODO v5.7 don't reserve multiple large buffers when decoding batches + Right compressed -> concatMap (either (pure . Left) parseChatMessages) . L.toList $ decompressBatch maxEncodedMsgLength compressed + +compressedBatchMsgBody_ :: MsgBody -> ByteString +compressedBatchMsgBody_ = markCompressedBatch . smpEncode . (L.:| []) . compress1 + +markCompressedBatch :: ByteString -> ByteString +markCompressedBatch = B.cons 'X' +{-# INLINE markCompressedBatch #-} parseMsgContainer :: J.Object -> JT.Parser MsgContainer parseMsgContainer v = diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index d5f66224b9..857198a109 100644 --- a/src/Simplex/Chat/Remote.hs +++ b/src/Simplex/Chat/Remote.hs @@ -72,11 +72,11 @@ import UnliftIO.Directory (copyFile, createDirectoryIfMissing, doesDirectoryExis -- when acting as host minRemoteCtrlVersion :: AppVersion -minRemoteCtrlVersion = AppVersion [5, 5, 0, 2] +minRemoteCtrlVersion = AppVersion [5, 6, 0, 0] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [5, 5, 0, 2] +minRemoteHostVersion = AppVersion [5, 6, 0, 0] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version diff --git a/src/Simplex/Chat/Store/AppSettings.hs b/src/Simplex/Chat/Store/AppSettings.hs new file mode 100644 index 0000000000..ee0dd30183 --- /dev/null +++ b/src/Simplex/Chat/Store/AppSettings.hs @@ -0,0 +1,22 @@ +{-# LANGUAGE OverloadedStrings #-} + +module Simplex.Chat.Store.AppSettings where + +import Control.Monad (join) +import Control.Monad.IO.Class (liftIO) +import qualified Data.Aeson as J +import Data.Maybe (fromMaybe) +import Database.SQLite.Simple (Only (..)) +import Simplex.Chat.AppSettings (AppSettings (..), combineAppSettings, defaultAppSettings, defaultParseAppSettings) +import Simplex.Messaging.Agent.Store.SQLite (maybeFirstRow) +import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB + +saveAppSettings :: DB.Connection -> AppSettings -> IO () +saveAppSettings db appSettings = do + DB.execute_ db "DELETE FROM app_settings" + DB.execute db "INSERT INTO app_settings (app_settings) VALUES (?)" (Only $ J.encode appSettings) + +getAppSettings :: DB.Connection -> Maybe AppSettings -> IO AppSettings +getAppSettings db platformDefaults = do + stored_ <- join <$> liftIO (maybeFirstRow (J.decodeStrict . fromOnly) $ DB.query_ db "SELECT app_settings FROM app_settings") + pure $ combineAppSettings (fromMaybe defaultAppSettings platformDefaults) (fromMaybe defaultParseAppSettings stored_) diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index fdc3703219..f8e9fa3401 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -34,10 +34,10 @@ import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (ConnId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, firstRow', maybeFirstRow) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB +import Simplex.Messaging.Crypto.Ratchet (PQSupport) import Simplex.Messaging.Util (eitherToMaybe) -import Simplex.Messaging.Version (VersionRange) -getConnectionEntity :: DB.Connection -> VersionRange -> User -> AgentConnId -> ExceptT StoreError IO ConnectionEntity +getConnectionEntity :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> AgentConnId -> ExceptT StoreError IO ConnectionEntity getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do c@Connection {connType, entityId} <- getConnection_ case entityId of @@ -55,13 +55,14 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do where getConnection_ :: ExceptT StoreError IO Connection getConnection_ = ExceptT $ do - firstRow toConnection (SEConnectionNotFound agentConnId) $ + firstRow (toConnection vr) (SEConnectionNotFound agentConnId) $ DB.query db [sql| SELECT connection_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, group_link_id, custom_user_profile_id, - conn_status, conn_type, contact_conn_initiated, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at, security_code, security_code_verified_at, auth_err_counter, - peer_chat_min_version, peer_chat_max_version + conn_status, conn_type, contact_conn_initiated, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, + created_at, security_code, security_code_verified_at, pq_support, pq_encryption, pq_snd_enabled, pq_rcv_enabled, auth_err_counter, + conn_chat_version, peer_chat_min_version, peer_chat_max_version FROM connections WHERE user_id = ? AND agent_conn_id = ? |] @@ -157,7 +158,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do userContact_ [(cReq, groupId)] = Right UserContact {userContactLinkId, connReqContact = cReq, groupId} userContact_ _ = Left SEUserContactLinkNotFound -getConnectionEntityByConnReq :: DB.Connection -> VersionRange -> User -> (ConnReqInvitation, ConnReqInvitation) -> IO (Maybe ConnectionEntity) +getConnectionEntityByConnReq :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> (ConnReqInvitation, ConnReqInvitation) -> IO (Maybe ConnectionEntity) getConnectionEntityByConnReq db vr user@User {userId} (cReqSchema1, cReqSchema2) = do connId_ <- maybeFirstRow fromOnly $ @@ -168,7 +169,7 @@ getConnectionEntityByConnReq db vr user@User {userId} (cReqSchema1, cReqSchema2) -- multiple connections can have same via_contact_uri_hash if request was repeated; -- this function searches for latest connection with contact so that "known contact" plan would be chosen; -- deleted connections are filtered out to allow re-connecting via same contact address -getContactConnEntityByConnReqHash :: DB.Connection -> VersionRange -> User -> (ConnReqUriHash, ConnReqUriHash) -> IO (Maybe ConnectionEntity) +getContactConnEntityByConnReqHash :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> (ConnReqUriHash, ConnReqUriHash) -> IO (Maybe ConnectionEntity) getContactConnEntityByConnReqHash db vr user@User {userId} (cReqHash1, cReqHash2) = do connId_ <- maybeFirstRow fromOnly $ @@ -188,7 +189,7 @@ getContactConnEntityByConnReqHash db vr user@User {userId} (cReqHash1, cReqHash2 (userId, cReqHash1, cReqHash2, ConnDeleted) maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getConnectionEntity db vr user) connId_ -getConnectionsToSubscribe :: DB.Connection -> VersionRange -> IO ([ConnId], [ConnectionEntity]) +getConnectionsToSubscribe :: DB.Connection -> (PQSupport -> VersionRangeChat) -> IO ([ConnId], [ConnectionEntity]) getConnectionsToSubscribe db vr = do aConnIds <- map fromOnly <$> DB.query_ db "SELECT agent_conn_id FROM connections where to_subscribe = 1" entities <- forM aConnIds $ \acId -> do diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index b844317593..47174a59a6 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -89,6 +89,7 @@ import Simplex.Chat.Types.Preferences import Simplex.Messaging.Agent.Protocol (ConnId, InvitationId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB +import Simplex.Messaging.Crypto.Ratchet (PQSupport) import Simplex.Messaging.Protocol (SubscriptionMode (..)) import Simplex.Messaging.Version @@ -124,14 +125,14 @@ deletePendingContactConnection db userId connId = |] (userId, connId, ConnContact) -createAddressContactConnection :: DB.Connection -> User -> Contact -> ConnId -> ConnReqUriHash -> XContactId -> Maybe Profile -> SubscriptionMode -> ExceptT StoreError IO Contact -createAddressContactConnection db user@User {userId} Contact {contactId} acId cReqHash xContactId incognitoProfile subMode = do - PendingContactConnection {pccConnId} <- liftIO $ createConnReqConnection db userId acId cReqHash xContactId incognitoProfile Nothing subMode +createAddressContactConnection :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Contact -> ConnId -> ConnReqUriHash -> XContactId -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> ExceptT StoreError IO Contact +createAddressContactConnection db vr user@User {userId} Contact {contactId} acId cReqHash xContactId incognitoProfile subMode chatV pqSup = do + PendingContactConnection {pccConnId} <- liftIO $ createConnReqConnection db userId acId cReqHash xContactId incognitoProfile Nothing subMode chatV pqSup liftIO $ DB.execute db "UPDATE connections SET contact_id = ? WHERE connection_id = ?" (contactId, pccConnId) - getContact db user contactId + getContact db vr user contactId -createConnReqConnection :: DB.Connection -> UserId -> ConnId -> ConnReqUriHash -> XContactId -> Maybe Profile -> Maybe GroupLinkId -> SubscriptionMode -> IO PendingContactConnection -createConnReqConnection db userId acId cReqHash xContactId incognitoProfile groupLinkId subMode = do +createConnReqConnection :: DB.Connection -> UserId -> ConnId -> ConnReqUriHash -> XContactId -> Maybe Profile -> Maybe GroupLinkId -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection +createConnReqConnection db userId acId cReqHash xContactId incognitoProfile groupLinkId subMode chatV pqSup = do createdAt <- getCurrentTime customUserProfileId <- mapM (createIncognitoProfile_ db userId createdAt) incognitoProfile let pccConnStatus = ConnJoined @@ -140,16 +141,20 @@ createConnReqConnection db userId acId cReqHash xContactId incognitoProfile grou [sql| INSERT INTO connections ( user_id, agent_conn_id, conn_status, conn_type, contact_conn_initiated, - via_contact_uri_hash, xcontact_id, custom_user_profile_id, via_group_link, group_link_id, created_at, updated_at, to_subscribe - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) + via_contact_uri_hash, xcontact_id, custom_user_profile_id, via_group_link, group_link_id, + created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] - ((userId, acId, pccConnStatus, ConnContact, True, cReqHash, xContactId) :. (customUserProfileId, isJust groupLinkId, groupLinkId, createdAt, createdAt, subMode == SMOnlyCreate)) + ( (userId, acId, pccConnStatus, ConnContact, True, cReqHash, xContactId) + :. (customUserProfileId, isJust groupLinkId, groupLinkId) + :. (createdAt, createdAt, subMode == SMOnlyCreate, chatV, pqSup, pqSup) + ) pccConnId <- insertedRowId db pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = True, viaUserContactLink = Nothing, groupLinkId, customUserProfileId, connReqInv = Nothing, localAlias = "", createdAt, updatedAt = createdAt} -getConnReqContactXContactId :: DB.Connection -> User -> ConnReqUriHash -> IO (Maybe Contact, Maybe XContactId) -getConnReqContactXContactId db user@User {userId} cReqHash = do - getContactByConnReqHash db user cReqHash >>= \case +getConnReqContactXContactId :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ConnReqUriHash -> IO (Maybe Contact, Maybe XContactId) +getConnReqContactXContactId db vr user@User {userId} cReqHash = do + getContactByConnReqHash db vr user cReqHash >>= \case c@(Just _) -> pure (c, Nothing) Nothing -> (Nothing,) <$> getXContactId where @@ -161,9 +166,9 @@ getConnReqContactXContactId db user@User {userId} cReqHash = do "SELECT xcontact_id FROM connections WHERE user_id = ? AND via_contact_uri_hash = ? LIMIT 1" (userId, cReqHash) -getContactByConnReqHash :: DB.Connection -> User -> ConnReqUriHash -> IO (Maybe Contact) -getContactByConnReqHash db user@User {userId} cReqHash = - maybeFirstRow (toContact user) $ +getContactByConnReqHash :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ConnReqUriHash -> IO (Maybe Contact) +getContactByConnReqHash db vr user@User {userId} cReqHash = + maybeFirstRow (toContact vr user) $ DB.query db [sql| @@ -173,8 +178,8 @@ getContactByConnReqHash db user@User {userId} cReqHash = cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, - c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter, - c.peer_chat_min_version, c.peer_chat_max_version + c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version FROM contacts ct JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id JOIN connections c ON c.contact_id = ct.contact_id @@ -184,8 +189,8 @@ getContactByConnReqHash db user@User {userId} cReqHash = |] (userId, cReqHash, CSActive) -createDirectConnection :: DB.Connection -> User -> ConnId -> ConnReqInvitation -> ConnStatus -> Maybe Profile -> SubscriptionMode -> IO PendingContactConnection -createDirectConnection db User {userId} acId cReq pccConnStatus incognitoProfile subMode = do +createDirectConnection :: DB.Connection -> User -> ConnId -> ConnReqInvitation -> ConnStatus -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection +createDirectConnection db User {userId} acId cReq pccConnStatus incognitoProfile subMode chatV pqSup = do createdAt <- getCurrentTime customUserProfileId <- mapM (createIncognitoProfile_ db userId createdAt) incognitoProfile let contactConnInitiated = pccConnStatus == ConnNew @@ -193,9 +198,13 @@ createDirectConnection db User {userId} acId cReq pccConnStatus incognitoProfile db [sql| INSERT INTO connections - (user_id, agent_conn_id, conn_req_inv, conn_status, conn_type, contact_conn_initiated, custom_user_profile_id, created_at, updated_at, to_subscribe) VALUES (?,?,?,?,?,?,?,?,?,?) + (user_id, agent_conn_id, conn_req_inv, conn_status, conn_type, contact_conn_initiated, custom_user_profile_id, + created_at, updated_at, to_subscribe, conn_chat_version, pq_support, pq_encryption) + VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) |] - (userId, acId, cReq, pccConnStatus, ConnContact, contactConnInitiated, customUserProfileId, createdAt, createdAt, subMode == SMOnlyCreate) + ( (userId, acId, cReq, pccConnStatus, ConnContact, contactConnInitiated, customUserProfileId) + :. (createdAt, createdAt, subMode == SMOnlyCreate, chatV, pqSup, pqSup) + ) pccConnId <- insertedRowId db pure PendingContactConnection {pccConnId, pccAgentConnId = AgentConnId acId, pccConnStatus, viaContactUri = False, viaUserContactLink = Nothing, groupLinkId = Nothing, customUserProfileId, connReqInv = Just cReq, localAlias = "", createdAt, updatedAt = createdAt} @@ -269,13 +278,13 @@ setContactDeleted db user@User {userId} ct@Contact {contactId} = do currentTs <- getCurrentTime DB.execute db "UPDATE contacts SET deleted = 1, updated_at = ? WHERE user_id = ? AND contact_id = ?" (currentTs, userId, contactId) -getDeletedContacts :: DB.Connection -> User -> IO [Contact] -getDeletedContacts db user@User {userId} = do +getDeletedContacts :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> IO [Contact] +getDeletedContacts db vr user@User {userId} = do contactIds <- map fromOnly <$> DB.query db "SELECT contact_id FROM contacts WHERE user_id = ? AND deleted = 1" (Only userId) - rights <$> mapM (runExceptT . getDeletedContact db user) contactIds + rights <$> mapM (runExceptT . getDeletedContact db vr user) contactIds -getDeletedContact :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO Contact -getDeletedContact db user contactId = getContact_ db user contactId True +getDeletedContact :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Int64 -> ExceptT StoreError IO Contact +getDeletedContact db vr user contactId = getContact_ db vr user contactId True deleteContactProfile_ :: DB.Connection -> UserId -> ContactId -> IO () deleteContactProfile_ db userId contactId = @@ -511,19 +520,19 @@ updateContactLDN_ db user@User {userId} contactId displayName newName updatedAt (newName, updatedAt, userId, contactId) safeDeleteLDN db user displayName -getContactByName :: DB.Connection -> User -> ContactName -> ExceptT StoreError IO Contact -getContactByName db user localDisplayName = do +getContactByName :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ContactName -> ExceptT StoreError IO Contact +getContactByName db vr user localDisplayName = do cId <- getContactIdByName db user localDisplayName - getContact db user cId + getContact db vr user cId -getUserContacts :: DB.Connection -> User -> IO [Contact] -getUserContacts db user@User {userId} = do +getUserContacts :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> IO [Contact] +getUserContacts db vr user@User {userId} = do contactIds <- map fromOnly <$> DB.query db "SELECT contact_id FROM contacts WHERE user_id = ? AND deleted = 0" (Only userId) - contacts <- rights <$> mapM (runExceptT . getContact db user) contactIds + contacts <- rights <$> mapM (runExceptT . getContact db vr user) contactIds pure $ filter (\Contact {activeConn} -> isJust activeConn) contacts -createOrUpdateContactRequest :: DB.Connection -> User -> Int64 -> InvitationId -> VersionRange -> Profile -> Maybe XContactId -> ExceptT StoreError IO ContactOrRequest -createOrUpdateContactRequest db user@User {userId} userContactLinkId invId (VersionRange minV maxV) Profile {displayName, fullName, image, contactLink, preferences} xContactId_ = +createOrUpdateContactRequest :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Int64 -> InvitationId -> VersionRangeChat -> Profile -> Maybe XContactId -> PQSupport -> ExceptT StoreError IO ContactOrRequest +createOrUpdateContactRequest db vr user@User {userId} userContactLinkId invId (VersionRange minV maxV) Profile {displayName, fullName, image, contactLink, preferences} xContactId_ pqSup = liftIO (maybeM getContact' xContactId_) >>= \case Just contact -> pure $ CORContact contact Nothing -> CORRequest <$> createOrUpdate_ @@ -552,14 +561,17 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId (Vers db [sql| INSERT INTO contact_requests - (user_contact_link_id, agent_invitation_id, peer_chat_min_version, peer_chat_max_version, contact_profile_id, local_display_name, user_id, created_at, updated_at, xcontact_id) - VALUES (?,?,?,?,?,?,?,?,?,?) + (user_contact_link_id, agent_invitation_id, peer_chat_min_version, peer_chat_max_version, contact_profile_id, local_display_name, user_id, + created_at, updated_at, xcontact_id, pq_support) + VALUES (?,?,?,?,?,?,?,?,?,?,?) |] - (userContactLinkId, invId, minV, maxV, profileId, ldn, userId, currentTs, currentTs, xContactId_) + ( (userContactLinkId, invId, minV, maxV, profileId, ldn, userId) + :. (currentTs, currentTs, xContactId_, pqSup) + ) insertedRowId db getContact' :: XContactId -> IO (Maybe Contact) getContact' xContactId = - maybeFirstRow (toContact user) $ + maybeFirstRow (toContact vr user) $ DB.query db [sql| @@ -569,8 +581,8 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId (Vers cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, - c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter, - c.peer_chat_min_version, c.peer_chat_max_version + c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version FROM contacts ct JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id LEFT JOIN connections c ON c.contact_id = ct.contact_id @@ -587,7 +599,7 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId (Vers [sql| SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_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, p.preferences, cr.created_at, cr.updated_at, + 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 JOIN connections c USING (user_contact_link_id) @@ -608,20 +620,20 @@ createOrUpdateContactRequest db user@User {userId} userContactLinkId invId (Vers db [sql| UPDATE contact_requests - SET agent_invitation_id = ?, peer_chat_min_version = ?, peer_chat_max_version = ?, updated_at = ? + SET agent_invitation_id = ?, pq_support = ?, peer_chat_min_version = ?, peer_chat_max_version = ?, updated_at = ? WHERE user_id = ? AND contact_request_id = ? |] - (invId, minV, maxV, currentTs, userId, cReqId) + (invId, pqSup, minV, maxV, currentTs, userId, cReqId) else withLocalDisplayName db userId displayName $ \ldn -> Right <$> do DB.execute db [sql| UPDATE contact_requests - SET agent_invitation_id = ?, peer_chat_min_version = ?, peer_chat_max_version = ?, local_display_name = ?, updated_at = ? + SET agent_invitation_id = ?, pq_support = ?, peer_chat_min_version = ?, peer_chat_max_version = ?, local_display_name = ?, updated_at = ? WHERE user_id = ? AND contact_request_id = ? |] - (invId, minV, maxV, ldn, currentTs, userId, cReqId) + (invId, pqSup, minV, maxV, ldn, currentTs, userId, cReqId) safeDeleteLDN db user oldLdn where updateProfile currentTs = @@ -656,7 +668,7 @@ getContactRequest db User {userId} contactRequestId = [sql| SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_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, p.preferences, cr.created_at, cr.updated_at, + 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 JOIN connections c USING (user_contact_link_id) @@ -697,8 +709,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 -> VersionRange -> ContactName -> ProfileId -> Profile -> Int64 -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> Bool -> IO Contact -createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}} agentConnId cReqChatVRange localDisplayName profileId profile userContactLinkId xContactId incognitoProfile subMode contactUsed = do +createAcceptedContact :: DB.Connection -> User -> ConnId -> VersionChat -> VersionRangeChat -> ContactName -> ProfileId -> Profile -> Int64 -> Maybe XContactId -> Maybe IncognitoProfile -> SubscriptionMode -> PQSupport -> Bool -> IO Contact +createAcceptedContact db user@User {userId, profile = LocalProfile {preferences}} agentConnId 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) createdAt <- getCurrentTime customUserProfileId <- forM incognitoProfile $ \case @@ -710,7 +722,7 @@ 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 cReqChatVRange Nothing (Just userContactLinkId) customUserProfileId 0 createdAt subMode + conn <- createConnection_ db userId ConnContact (Just contactId) agentConnId 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 = createdAt, updatedAt = createdAt, chatTs = Just createdAt, contactGroupMemberId = Nothing, contactGrpInvSent = False} @@ -719,12 +731,12 @@ getContactIdByName db User {userId} cName = ExceptT . firstRow fromOnly (SEContactNotFoundByName cName) $ DB.query db "SELECT contact_id FROM contacts WHERE user_id = ? AND local_display_name = ? AND deleted = 0" (userId, cName) -getContact :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO Contact -getContact db user contactId = getContact_ db user contactId False +getContact :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Int64 -> ExceptT StoreError IO Contact +getContact db vr user contactId = getContact_ db vr user contactId False -getContact_ :: DB.Connection -> User -> Int64 -> Bool -> ExceptT StoreError IO Contact -getContact_ db user@User {userId} contactId deleted = - ExceptT . firstRow (toContact user) (SEContactNotFound contactId) $ +getContact_ :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Int64 -> Bool -> ExceptT StoreError IO Contact +getContact_ db vr user@User {userId} contactId deleted = + ExceptT . firstRow (toContact vr user) (SEContactNotFound contactId) $ DB.query db [sql| @@ -734,8 +746,8 @@ getContact_ db user@User {userId} contactId deleted = cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, -- Connection c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, - c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter, - c.peer_chat_min_version, c.peer_chat_max_version + c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version FROM contacts ct JOIN contact_profiles cp ON ct.contact_profile_id = cp.contact_profile_id LEFT JOIN connections c ON c.contact_id = ct.contact_id @@ -778,8 +790,8 @@ getPendingContactConnections db User {userId} = do |] [":user_id" := userId, ":conn_type" := ConnContact] -getContactConnections :: DB.Connection -> UserId -> Contact -> IO [Connection] -getContactConnections db userId Contact {contactId} = +getContactConnections :: DB.Connection -> (PQSupport -> VersionRangeChat) -> UserId -> Contact -> IO [Connection] +getContactConnections db vr userId Contact {contactId} = connections =<< liftIO getConnections_ where getConnections_ = @@ -787,25 +799,27 @@ getContactConnections db userId Contact {contactId} = db [sql| SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter, - c.peer_chat_min_version, c.peer_chat_max_version + c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, + c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version FROM connections c JOIN contacts ct ON ct.contact_id = c.contact_id WHERE c.user_id = ? AND ct.user_id = ? AND ct.contact_id = ? |] (userId, userId, contactId) connections [] = pure [] - connections rows = pure $ map toConnection rows + connections rows = pure $ map (toConnection vr) rows -getConnectionById :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO Connection -getConnectionById db User {userId} connId = ExceptT $ do - firstRow toConnection (SEConnectionNotFoundById connId) $ +getConnectionById :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Int64 -> ExceptT StoreError IO Connection +getConnectionById db vr User {userId} connId = ExceptT $ do + firstRow (toConnection vr) (SEConnectionNotFoundById connId) $ DB.query db [sql| SELECT connection_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, group_link_id, custom_user_profile_id, - conn_status, conn_type, contact_conn_initiated, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at, security_code, security_code_verified_at, auth_err_counter, - peer_chat_min_version, peer_chat_max_version + conn_status, conn_type, contact_conn_initiated, local_alias, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, + created_at, security_code, security_code_verified_at, pq_support, pq_encryption, pq_snd_enabled, pq_rcv_enabled, auth_err_counter, + conn_chat_version, peer_chat_min_version, peer_chat_max_version FROM connections WHERE user_id = ? AND connection_id = ? |] diff --git a/src/Simplex/Chat/Store/Files.hs b/src/Simplex/Chat/Store/Files.hs index d2351b4005..e77681bb9b 100644 --- a/src/Simplex/Chat/Store/Files.hs +++ b/src/Simplex/Chat/Store/Files.hs @@ -38,6 +38,7 @@ module Simplex.Chat.Store.Files getGroupFileIdBySharedMsgId, getDirectFileIdBySharedMsgId, getChatRefByFileId, + lookupChatRefByFileId, updateSndFileStatus, createSndFileChunk, updateSndFileChunkMsg, @@ -45,6 +46,7 @@ module Simplex.Chat.Store.Files deleteSndFileChunks, createRcvFileTransfer, createRcvGroupFileTransfer, + createRcvStandaloneFileTransfer, appendRcvFD, getRcvFileDescrByRcvFileId, getRcvFileDescrBySndFileId, @@ -69,6 +71,7 @@ module Simplex.Chat.Store.Files getFileTransfer, getFileTransferProgress, getFileTransferMeta, + lookupFileTransferRedirectMeta, getSndFileTransfer, getSndFileTransfers, getContactFileInfo, @@ -85,12 +88,14 @@ import Control.Monad import Control.Monad.Except import Control.Monad.IO.Class import Data.Either (rights) +import Data.Functor ((<&>)) import Data.Int (Int64) import Data.Maybe (fromMaybe, isJust, listToMaybe) import Data.Text (Text) import Data.Time (addUTCTime) import Data.Time.Clock (UTCTime (..), getCurrentTime, nominalDay) import Data.Type.Equality +import Data.Word (Word32) import Database.SQLite.Simple (Only (..), (:.) (..)) import Database.SQLite.Simple.QQ (sql) import Database.SQLite.Simple.ToField (ToField) @@ -109,8 +114,9 @@ import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.File as CF +import Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Protocol (SubscriptionMode (..)) -import Simplex.Messaging.Version (VersionRange) +import Simplex.Messaging.Version import System.FilePath (takeFileName) getLiveSndFileTransfers :: DB.Connection -> User -> IO [SndFileTransfer] @@ -168,10 +174,10 @@ getPendingSndChunks db fileId connId = |] (fileId, connId) -createSndDirectFTConnection :: DB.Connection -> User -> Int64 -> (CommandId, ConnId) -> SubscriptionMode -> IO () -createSndDirectFTConnection db user@User {userId} fileId (cmdId, acId) subMode = do +createSndDirectFTConnection :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Int64 -> (CommandId, ConnId) -> SubscriptionMode -> IO () +createSndDirectFTConnection db vr user@User {userId} fileId (cmdId, acId) subMode = do currentTs <- getCurrentTime - Connection {connId} <- createSndFileConnection_ db userId fileId acId subMode + Connection {connId} <- createSndFileConnection_ db vr userId fileId acId subMode setCommandConnId db user cmdId connId DB.execute db @@ -186,12 +192,12 @@ createSndGroupFileTransfer db userId GroupInfo {groupId} filePath FileInvitation "INSERT INTO files (user_id, group_id, file_name, file_path, file_size, chunk_size, file_inline, ci_file_status, protocol, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?)" ((userId, groupId, fileName, filePath, fileSize, chunkSize) :. (fileInline, CIFSSndStored, FPSMP, currentTs, currentTs)) fileId <- insertedRowId db - pure FileTransferMeta {fileId, xftpSndFile = Nothing, fileName, filePath, fileSize, fileInline, chunkSize, cancelled = False} + pure FileTransferMeta {fileId, xftpSndFile = Nothing, xftpRedirectFor = Nothing, fileName, filePath, fileSize, fileInline, chunkSize, cancelled = False} -createSndGroupFileTransferConnection :: DB.Connection -> User -> Int64 -> (CommandId, ConnId) -> GroupMember -> SubscriptionMode -> IO () -createSndGroupFileTransferConnection db user@User {userId} fileId (cmdId, acId) GroupMember {groupMemberId} subMode = do +createSndGroupFileTransferConnection :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Int64 -> (CommandId, ConnId) -> GroupMember -> SubscriptionMode -> IO () +createSndGroupFileTransferConnection db vr user@User {userId} fileId (cmdId, acId) GroupMember {groupMemberId} subMode = do currentTs <- getCurrentTime - Connection {connId} <- createSndFileConnection_ db userId fileId acId subMode + Connection {connId} <- createSndFileConnection_ db vr userId fileId acId subMode setCommandConnId db user cmdId connId DB.execute db @@ -259,16 +265,16 @@ getSndFTViaMsgDelivery db User {userId} Connection {connId, agentConnId} agentMs (\n -> SndFileTransfer {fileId, fileStatus, fileName, fileSize, chunkSize, filePath, fileDescrId, fileInline, groupMemberId, recipientDisplayName = n, connId, agentConnId}) <$> (contactName_ <|> memberName_) -createSndFileTransferXFTP :: DB.Connection -> User -> ContactOrGroup -> CryptoFile -> FileInvitation -> AgentSndFileId -> Integer -> IO FileTransferMeta -createSndFileTransferXFTP db User {userId} contactOrGroup (CryptoFile filePath cryptoArgs) FileInvitation {fileName, fileSize} agentSndFileId chunkSize = do +createSndFileTransferXFTP :: DB.Connection -> User -> Maybe ContactOrGroup -> CryptoFile -> FileInvitation -> AgentSndFileId -> Maybe FileTransferId -> Integer -> IO FileTransferMeta +createSndFileTransferXFTP db User {userId} contactOrGroup_ (CryptoFile filePath cryptoArgs) FileInvitation {fileName, fileSize} agentSndFileId xftpRedirectFor chunkSize = do currentTs <- getCurrentTime let xftpSndFile = Just XFTPSndFile {agentSndFileId, privateSndFileDescr = Nothing, agentSndFileDeleted = False, cryptoArgs} DB.execute db - "INSERT INTO files (contact_id, group_id, user_id, file_name, file_path, file_crypto_key, file_crypto_nonce, file_size, chunk_size, agent_snd_file_id, ci_file_status, protocol, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?)" - (contactAndGroupIds contactOrGroup :. (userId, fileName, filePath, CF.fileKey <$> cryptoArgs, CF.fileNonce <$> cryptoArgs, fileSize, chunkSize, agentSndFileId, CIFSSndStored, FPXFTP, currentTs, currentTs)) + "INSERT INTO files (contact_id, group_id, user_id, file_name, file_path, file_crypto_key, file_crypto_nonce, file_size, chunk_size, redirect_file_id, agent_snd_file_id, ci_file_status, protocol, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?)" + (maybe (Nothing, Nothing) contactAndGroupIds contactOrGroup_ :. (userId, fileName, filePath, CF.fileKey <$> cryptoArgs, CF.fileNonce <$> cryptoArgs, fileSize, chunkSize) :. (xftpRedirectFor, agentSndFileId, CIFSSndStored, FPXFTP, currentTs, currentTs)) fileId <- insertedRowId db - pure FileTransferMeta {fileId, xftpSndFile, fileName, filePath, fileSize, fileInline = Nothing, chunkSize, cancelled = False} + pure FileTransferMeta {fileId, xftpSndFile, xftpRedirectFor, fileName, filePath, fileSize, fileInline = Nothing, chunkSize, cancelled = False} createSndFTDescrXFTP :: DB.Connection -> User -> Maybe GroupMember -> Connection -> FileTransferMeta -> FileDescr -> IO () createSndFTDescrXFTP db User {userId} m Connection {connId} FileTransferMeta {fileId} FileDescr {fileDescrText, fileDescrPartNo, fileDescrComplete} = do @@ -403,11 +409,14 @@ getDirectFileIdBySharedMsgId db User {userId} Contact {contactId} sharedMsgId = (userId, contactId, sharedMsgId) getChatRefByFileId :: DB.Connection -> User -> Int64 -> ExceptT StoreError IO ChatRef -getChatRefByFileId db User {userId} fileId = - liftIO getChatRef >>= \case - [(Just contactId, Nothing)] -> pure $ ChatRef CTDirect contactId - [(Nothing, Just groupId)] -> pure $ ChatRef CTGroup groupId - _ -> throwError $ SEInternalError "could not retrieve chat ref by file id" +getChatRefByFileId db user fileId = liftIO (lookupChatRefByFileId db user fileId) >>= maybe (throwError $ SEInternalError "could not retrieve chat ref by file id") pure + +lookupChatRefByFileId :: DB.Connection -> User -> Int64 -> IO (Maybe ChatRef) +lookupChatRefByFileId db User {userId} fileId = + getChatRef <&> \case + [(Just contactId, Nothing)] -> Just $ ChatRef CTDirect contactId + [(Nothing, Just groupId)] -> Just $ ChatRef CTGroup groupId + _ -> Nothing where getChatRef = DB.query @@ -420,10 +429,11 @@ getChatRefByFileId db User {userId} fileId = |] (userId, fileId) -createSndFileConnection_ :: DB.Connection -> UserId -> Int64 -> ConnId -> SubscriptionMode -> IO Connection -createSndFileConnection_ db userId fileId agentConnId subMode = do +-- TODO v6.0 remove +createSndFileConnection_ :: DB.Connection -> (PQSupport -> VersionRangeChat) -> UserId -> Int64 -> ConnId -> SubscriptionMode -> IO Connection +createSndFileConnection_ db vr userId fileId agentConnId subMode = do currentTs <- getCurrentTime - createConnection_ db userId ConnSndFile (Just fileId) agentConnId chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode + createConnection_ db userId ConnSndFile (Just fileId) agentConnId (minVersion $ vr PQSupportOff) chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode CR.PQSupportOff updateSndFileStatus :: DB.Connection -> SndFileTransfer -> FileStatus -> IO () updateSndFileStatus db SndFileTransfer {fileId, connId} status = do @@ -518,6 +528,23 @@ createRcvGroupFileTransfer db userId GroupMember {groupId, groupMemberId, localD (fileId, FSNew, fileConnReq, fileInline, rcvFileInline, groupMemberId, rfdId, currentTs, currentTs) pure RcvFileTransfer {fileId, xftpRcvFile, fileInvitation = f, fileStatus = RFSNew, rcvFileInline, senderDisplayName = c, chunkSize, cancelled = False, grpMemberId = Just groupMemberId, cryptoArgs = Nothing} +createRcvStandaloneFileTransfer :: DB.Connection -> UserId -> CryptoFile -> Int64 -> Word32 -> ExceptT StoreError IO Int64 +createRcvStandaloneFileTransfer db userId (CryptoFile filePath cfArgs_) fileSize chunkSize = do + currentTs <- liftIO getCurrentTime + fileId <- liftIO $ do + DB.execute + db + "INSERT INTO files (user_id, file_name, file_path, file_size, chunk_size, ci_file_status, protocol, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" + (userId, takeFileName filePath, filePath, fileSize, chunkSize, CIFSRcvInvitation, FPXFTP, currentTs, currentTs) + insertedRowId db + liftIO . forM_ cfArgs_ $ \cfArgs -> setFileCryptoArgs_ db fileId cfArgs currentTs + liftIO $ + DB.execute + db + "INSERT INTO rcv_files (file_id, file_status, created_at, updated_at) VALUES (?,?,?,?)" + (fileId, FSNew, currentTs, currentTs) + pure fileId + createRcvFD_ :: DB.Connection -> UserId -> UTCTime -> FileDescr -> ExceptT StoreError IO RcvFileDescr createRcvFD_ db userId currentTs FileDescr {fileDescrText, fileDescrPartNo, fileDescrComplete} = do when (fileDescrPartNo /= 0) $ throwError SERcvFileInvalidDescrPart @@ -644,9 +671,9 @@ getRcvFileTransfer_ db userId fileId = do (FileStatus, Maybe ConnReqInvitation, Maybe Int64, String, Integer, Integer, Maybe Bool) :. (Maybe ContactName, Maybe ContactName, Maybe FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe InlineFileMode, Maybe AgentRcvFileId, Bool) :. (Maybe Int64, Maybe AgentConnId) -> ExceptT StoreError IO RcvFileTransfer rcvFileTransfer rfd_ ((fileStatus', fileConnReq, grpMemberId, fileName, fileSize, chunkSize, cancelled_) :. (contactName_, memberName_, filePath_, fileKey, fileNonce, fileInline, rcvFileInline, agentRcvFileId, agentRcvFileDeleted) :. (connId_, agentConnId_)) = - case contactName_ <|> memberName_ of + case contactName_ <|> memberName_ <|> standaloneName_ of Nothing -> throwError $ SERcvFileInvalid fileId - Just name -> do + Just name -> case fileStatus' of FSNew -> pure $ ft name RFSNew FSAccepted -> ft name . RFSAccepted <$> rfi @@ -654,6 +681,9 @@ getRcvFileTransfer_ db userId fileId = do FSComplete -> ft name . RFSComplete <$> rfi FSCancelled -> ft name . RFSCancelled <$> rfi_ where + standaloneName_ = case (connId_, agentRcvFileId, filePath_) of + (Nothing, Just _, Just _) -> Just "" -- filePath marks files that are accepted from contact or, in this case, set by createRcvDirectFileTransfer + _ -> Nothing ft senderDisplayName fileStatus = let fileInvitation = FileInvitation {fileName, fileSize, fileDigest = Nothing, fileConnReq, fileInline, fileDescr = Nothing} cryptoArgs = CFArgs <$> fileKey <*> fileNonce @@ -665,7 +695,7 @@ getRcvFileTransfer_ db userId fileId = do _ -> pure Nothing cancelled = fromMaybe False cancelled_ -acceptRcvFileTransfer :: DB.Connection -> VersionRange -> User -> Int64 -> (CommandId, ConnId) -> ConnStatus -> FilePath -> SubscriptionMode -> ExceptT StoreError IO AChatItem +acceptRcvFileTransfer :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Int64 -> (CommandId, ConnId) -> ConnStatus -> FilePath -> SubscriptionMode -> ExceptT StoreError IO AChatItem acceptRcvFileTransfer db vr user@User {userId} fileId (cmdId, acId) connStatus filePath subMode = ExceptT $ do currentTs <- getCurrentTime acceptRcvFT_ db user fileId filePath Nothing currentTs @@ -677,16 +707,16 @@ acceptRcvFileTransfer db vr user@User {userId} fileId (cmdId, acId) connStatus f setCommandConnId db user cmdId connId runExceptT $ getChatItemByFileId db vr user fileId -getContactByFileId :: DB.Connection -> User -> FileTransferId -> ExceptT StoreError IO Contact -getContactByFileId db user@User {userId} fileId = do +getContactByFileId :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> FileTransferId -> ExceptT StoreError IO Contact +getContactByFileId db vr user@User {userId} fileId = do cId <- getContactIdByFileId - getContact db user cId + getContact db vr user cId where getContactIdByFileId = ExceptT . firstRow fromOnly (SEContactNotFoundByFileId fileId) $ DB.query db "SELECT contact_id FROM files WHERE user_id = ? AND file_id = ?" (userId, fileId) -acceptRcvInlineFT :: DB.Connection -> VersionRange -> User -> FileTransferId -> FilePath -> ExceptT StoreError IO AChatItem +acceptRcvInlineFT :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> FileTransferId -> FilePath -> ExceptT StoreError IO AChatItem acceptRcvInlineFT db vr user fileId filePath = do liftIO $ acceptRcvFT_ db user fileId filePath (Just IFMOffer) =<< getCurrentTime getChatItemByFileId db vr user fileId @@ -695,7 +725,7 @@ startRcvInlineFT :: DB.Connection -> User -> RcvFileTransfer -> FilePath -> Mayb startRcvInlineFT db user RcvFileTransfer {fileId} filePath rcvFileInline = acceptRcvFT_ db user fileId filePath rcvFileInline =<< getCurrentTime -xftpAcceptRcvFT :: DB.Connection -> VersionRange -> User -> FileTransferId -> FilePath -> ExceptT StoreError IO AChatItem +xftpAcceptRcvFT :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> FileTransferId -> FilePath -> ExceptT StoreError IO AChatItem xftpAcceptRcvFT db vr user fileId filePath = do liftIO $ acceptRcvFT_ db user fileId filePath Nothing =<< getCurrentTime getChatItemByFileId db vr user fileId @@ -888,17 +918,22 @@ getFileTransferMeta_ db userId fileId = DB.query db [sql| - SELECT file_name, file_size, chunk_size, file_path, file_crypto_key, file_crypto_nonce, file_inline, agent_snd_file_id, agent_snd_file_deleted, private_snd_file_descr, cancelled + SELECT file_name, file_size, chunk_size, file_path, file_crypto_key, file_crypto_nonce, file_inline, agent_snd_file_id, agent_snd_file_deleted, private_snd_file_descr, cancelled, redirect_file_id FROM files WHERE user_id = ? AND file_id = ? |] (userId, fileId) where - fileTransferMeta :: (String, Integer, Integer, FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe AgentSndFileId, Bool, Maybe Text, Maybe Bool) -> FileTransferMeta - fileTransferMeta (fileName, fileSize, chunkSize, filePath, fileKey, fileNonce, fileInline, aSndFileId_, agentSndFileDeleted, privateSndFileDescr, cancelled_) = + fileTransferMeta :: (String, Integer, Integer, FilePath, Maybe C.SbKey, Maybe C.CbNonce, Maybe InlineFileMode, Maybe AgentSndFileId, Bool, Maybe Text, Maybe Bool, Maybe FileTransferId) -> FileTransferMeta + fileTransferMeta (fileName, fileSize, chunkSize, filePath, fileKey, fileNonce, fileInline, aSndFileId_, agentSndFileDeleted, privateSndFileDescr, cancelled_, xftpRedirectFor) = let cryptoArgs = CFArgs <$> fileKey <*> fileNonce xftpSndFile = (\fId -> XFTPSndFile {agentSndFileId = fId, privateSndFileDescr, agentSndFileDeleted, cryptoArgs}) <$> aSndFileId_ - in FileTransferMeta {fileId, xftpSndFile, fileName, fileSize, chunkSize, filePath, fileInline, cancelled = fromMaybe False cancelled_} + in FileTransferMeta {fileId, xftpSndFile, xftpRedirectFor, fileName, fileSize, chunkSize, filePath, fileInline, cancelled = fromMaybe False cancelled_} + +lookupFileTransferRedirectMeta :: DB.Connection -> User -> Int64 -> IO [FileTransferMeta] +lookupFileTransferRedirectMeta db User {userId} fileId = do + redirects <- DB.query db "SELECT file_id FROM files WHERE user_id = ? AND redirect_file_id = ?" (userId, fileId) + rights <$> mapM (runExceptT . getFileTransferMeta_ db userId . fromOnly) redirects createLocalFile :: ToField (CIFileStatus d) => CIFileStatus d -> DB.Connection -> User -> NoteFolder -> ChatItemId -> UTCTime -> CryptoFile -> Integer -> Integer -> IO Int64 createLocalFile fileStatus db User {userId} NoteFolder {noteFolderId} chatItemId itemTs CryptoFile {filePath, cryptoArgs} fileSize fileChunkSize = do @@ -965,7 +1000,7 @@ getLocalCryptoFile db userId fileId sent = pure $ CryptoFile filePath fileCryptoArgs _ -> throwError $ SEFileNotFound fileId -updateDirectCIFileStatus :: forall d. MsgDirectionI d => DB.Connection -> VersionRange -> User -> Int64 -> CIFileStatus d -> ExceptT StoreError IO AChatItem +updateDirectCIFileStatus :: forall d. MsgDirectionI d => DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Int64 -> CIFileStatus d -> ExceptT StoreError IO AChatItem updateDirectCIFileStatus db vr user fileId fileStatus = do aci@(AChatItem cType d cInfo ci) <- getChatItemByFileId db vr user fileId case (cType, testEquality d $ msgDirection @d) of diff --git a/src/Simplex/Chat/Store/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index d82cc7570f..254b8dab59 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -4,6 +4,7 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RecordWildCards #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TupleSections #-} @@ -132,7 +133,7 @@ import Data.Time.Clock (UTCTime (..), getCurrentTime) import Database.SQLite.Simple (NamedParam (..), Only (..), Query (..), (:.) (..)) import Database.SQLite.Simple.QQ (sql) import Simplex.Chat.Messages -import Simplex.Chat.Protocol (groupForwardVRange) +import Simplex.Chat.Protocol (groupForwardVersion) import Simplex.Chat.Store.Direct import Simplex.Chat.Store.Shared import Simplex.Chat.Types @@ -141,6 +142,7 @@ import Simplex.Messaging.Agent.Protocol (ConnId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Crypto as C +import Simplex.Messaging.Crypto.Ratchet (PQSupport, pattern PQEncOff, pattern PQSupportOff) import Simplex.Messaging.Protocol (SubscriptionMode (..)) import Simplex.Messaging.Util (eitherToMaybe, ($>>=), (<$$>)) import Simplex.Messaging.Version @@ -148,13 +150,13 @@ import UnliftIO.STM type GroupInfoRow = (Int64, GroupName, GroupName, Text, Maybe Text, Maybe ImageData, Maybe ProfileId, Maybe MsgFilter, Maybe Bool, Bool, Maybe GroupPreferences) :. (UTCTime, UTCTime, Maybe UTCTime, Maybe UTCTime) :. GroupMemberRow -type GroupMemberRow = ((Int64, Int64, MemberId, Version, Version, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences)) +type GroupMemberRow = ((Int64, Int64, MemberId, VersionChat, VersionChat, GroupMemberRole, GroupMemberCategory, GroupMemberStatus, Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, ContactName, Maybe ContactId, ProfileId, ProfileId, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Maybe Preferences)) -type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe Version, Maybe Version, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences)) +type MaybeGroupMemberRow = ((Maybe Int64, Maybe Int64, Maybe MemberId, Maybe VersionChat, Maybe VersionChat, Maybe GroupMemberRole, Maybe GroupMemberCategory, Maybe GroupMemberStatus, Maybe Bool, Maybe MemberRestrictionStatus) :. (Maybe Int64, Maybe GroupMemberId, Maybe ContactName, Maybe ContactId, Maybe ProfileId, Maybe ProfileId, Maybe ContactName, Maybe Text, Maybe ImageData, Maybe ConnReqContact, Maybe LocalAlias, Maybe Preferences)) -toGroupInfo :: VersionRange -> Int64 -> GroupInfoRow -> GroupInfo +toGroupInfo :: (PQSupport -> VersionRangeChat) -> Int64 -> GroupInfoRow -> GroupInfo toGroupInfo vr userContactId ((groupId, localDisplayName, displayName, fullName, description, image, hostConnCustomUserProfileId, enableNtfs_, sendRcpts, favorite, groupPreferences) :. (createdAt, updatedAt, chatTs, userMemberProfileSentAt) :. userMemberRow) = - let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = JVersionRange vr} + let membership = (toGroupMember userContactId userMemberRow) {memberChatVRange = vr PQSupportOff} chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite} fullGroupPreferences = mergeGroupPreferences groupPreferences groupProfile = GroupProfile {displayName, fullName, description, image, groupPreferences} @@ -167,7 +169,7 @@ toGroupMember userContactId ((groupMemberId, groupId, memberId, minVer, maxVer, blockedByAdmin = maybe False mrsBlocked memberRestriction_ invitedBy = toInvitedBy userContactId invitedById activeConn = Nothing - memberChatVRange = JVersionRange $ fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer + memberChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer in GroupMember {..} toMaybeGroupMember :: Int64 -> MaybeGroupMemberRow -> Maybe GroupMember @@ -184,17 +186,18 @@ createGroupLink db User {userId} groupInfo@GroupInfo {groupId, localDisplayName} "INSERT INTO user_contact_links (user_id, group_id, group_link_id, local_display_name, conn_req_contact, group_link_member_role, auto_accept, created_at, updated_at) VALUES (?,?,?,?,?,?,?,?,?)" (userId, groupId, groupLinkId, "group_link_" <> localDisplayName, cReq, memberRole, True, currentTs, currentTs) userContactLinkId <- insertedRowId db - void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode + void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode PQSupportOff -getGroupLinkConnection :: DB.Connection -> User -> GroupInfo -> ExceptT StoreError IO Connection -getGroupLinkConnection db User {userId} groupInfo@GroupInfo {groupId} = - ExceptT . firstRow toConnection (SEGroupLinkNotFound groupInfo) $ +getGroupLinkConnection :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupInfo -> ExceptT StoreError IO Connection +getGroupLinkConnection db vr User {userId} groupInfo@GroupInfo {groupId} = + ExceptT . firstRow (toConnection vr) (SEGroupLinkNotFound groupInfo) $ DB.query db [sql| SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter, - c.peer_chat_min_version, c.peer_chat_max_version + c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, + c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version FROM connections c JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id WHERE c.user_id = ? AND uc.user_id = ? AND uc.group_id = ? @@ -258,7 +261,7 @@ setGroupLinkMemberRole :: DB.Connection -> User -> Int64 -> GroupMemberRole -> I setGroupLinkMemberRole db User {userId} userContactLinkId memberRole = DB.execute db "UPDATE user_contact_links SET group_link_member_role = ? WHERE user_id = ? AND user_contact_link_id = ?" (memberRole, userId, userContactLinkId) -getGroupAndMember :: DB.Connection -> User -> Int64 -> VersionRange -> ExceptT StoreError IO (GroupInfo, GroupMember) +getGroupAndMember :: DB.Connection -> User -> Int64 -> (PQSupport -> VersionRangeChat) -> ExceptT StoreError IO (GroupInfo, GroupMember) getGroupAndMember db User {userId, userContactId} groupMemberId vr = ExceptT . firstRow toGroupAndMember (SEInternalError "referenced group member not found") $ DB.query @@ -278,8 +281,9 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr = m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter, - c.peer_chat_min_version, c.peer_chat_max_version + c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, + c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version FROM group_members m JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) JOIN groups g ON g.group_id = m.group_id @@ -299,10 +303,10 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr = toGroupAndMember (groupInfoRow :. memberRow :. connRow) = let groupInfo = toGroupInfo vr userContactId groupInfoRow member = toGroupMember userContactId memberRow - in (groupInfo, (member :: GroupMember) {activeConn = toMaybeConnection connRow}) + in (groupInfo, (member :: GroupMember) {activeConn = toMaybeConnection vr connRow}) -- | creates completely new group with a single member - the current user -createNewGroup :: DB.Connection -> VersionRange -> TVar ChaChaDRG -> User -> GroupProfile -> Maybe Profile -> ExceptT StoreError IO GroupInfo +createNewGroup :: DB.Connection -> (PQSupport -> VersionRangeChat) -> TVar ChaChaDRG -> User -> GroupProfile -> Maybe Profile -> ExceptT StoreError IO GroupInfo createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = ExceptT $ do let GroupProfile {displayName, fullName, description, image, groupPreferences} = groupProfile fullGroupPreferences = mergeGroupPreferences groupPreferences @@ -344,7 +348,7 @@ createNewGroup db vr gVar user@User {userId} groupProfile incognitoProfile = Exc } -- | creates a new group record for the group the current user was invited to, or returns an existing one -createGroupInvitation :: DB.Connection -> VersionRange -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId) +createGroupInvitation :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Contact -> GroupInvitation -> Maybe ProfileId -> ExceptT StoreError IO (GroupInfo, GroupMemberId) createGroupInvitation _ _ _ Contact {localDisplayName, activeConn = Nothing} _ _ = throwError $ SEContactNotReady localDisplayName createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activeConn = Just Connection {customUserProfileId, peerChatVRange}} GroupInvitation {fromMember, invitedMember, connRequest, groupProfile} incognitoProfileId = do liftIO getInvitationGroupId_ >>= \case @@ -389,7 +393,7 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ |] (profileId, localDisplayName, connRequest, customUserProfileId, userId, True, currentTs, currentTs, currentTs, currentTs) insertedRowId db - let JVersionRange hostVRange = peerChatVRange + let hostVRange = const $ adjustedMemberVRange vr peerChatVRange GroupMember {groupMemberId} <- createContactMemberInv_ db user groupId Nothing contact fromMember GCHostMember GSMemInvited IBUnknown Nothing currentTs hostVRange membership <- createContactMemberInv_ db user groupId (Just groupMemberId) user invitedMember GCUserMember GSMemInvited (IBContact contactId) incognitoProfileId currentTs vr let chatSettings = ChatSettings {enableNtfs = MFAll, sendRcpts = Nothing, favorite = False} @@ -410,13 +414,18 @@ createGroupInvitation db vr user@User {userId} contact@Contact {contactId, activ groupMemberId ) +adjustedMemberVRange :: (PQSupport -> VersionRangeChat) -> VersionRangeChat -> VersionRangeChat +adjustedMemberVRange getVR vr@(VersionRange minV maxV) = + let maxV' = min maxV (maxVersion $ getVR PQSupportOff) + in fromMaybe vr $ safeVersionRange minV (max minV maxV') + getHostMemberId_ :: DB.Connection -> User -> GroupId -> ExceptT StoreError IO GroupMemberId getHostMemberId_ db User {userId} groupId = ExceptT . firstRow fromOnly (SEHostMemberIdNotFound groupId) $ DB.query db "SELECT group_member_id FROM group_members WHERE user_id = ? AND group_id = ? AND member_category = ?" (userId, groupId, GCHostMember) -createContactMemberInv_ :: IsContact a => DB.Connection -> User -> GroupId -> Maybe GroupMemberId -> a -> MemberIdRole -> GroupMemberCategory -> GroupMemberStatus -> InvitedBy -> Maybe ProfileId -> UTCTime -> VersionRange -> ExceptT StoreError IO GroupMember -createContactMemberInv_ db User {userId, userContactId} groupId invitedByGroupMemberId userOrContact MemberIdRole {memberId, memberRole} memberCategory memberStatus invitedBy incognitoProfileId createdAt memberChatVRange@(VersionRange minV maxV) = do +createContactMemberInv_ :: IsContact a => DB.Connection -> User -> GroupId -> Maybe GroupMemberId -> a -> MemberIdRole -> GroupMemberCategory -> GroupMemberStatus -> InvitedBy -> Maybe ProfileId -> UTCTime -> (PQSupport -> VersionRangeChat) -> ExceptT StoreError IO GroupMember +createContactMemberInv_ db User {userId, userContactId} groupId invitedByGroupMemberId userOrContact MemberIdRole {memberId, memberRole} memberCategory memberStatus invitedBy incognitoProfileId createdAt vr = do incognitoProfile <- forM incognitoProfileId $ \profileId -> getProfileById db userId profileId (localDisplayName, memberProfile) <- case (incognitoProfile, incognitoProfileId) of (Just profile@LocalProfile {displayName}, Just profileId) -> @@ -440,9 +449,10 @@ createContactMemberInv_ db User {userId, userContactId} groupId invitedByGroupMe memberContactId = Just $ contactId' userOrContact, memberContactProfileId = localProfileId (profile' userOrContact), activeConn = Nothing, - memberChatVRange = JVersionRange memberChatVRange + memberChatVRange } where + memberChatVRange@(VersionRange minV maxV) = vr PQSupportOff insertMember_ :: IO ContactName insertMember_ = do let localDisplayName = localDisplayName' userOrContact @@ -478,7 +488,7 @@ createContactMemberInv_ db User {userId, userContactId} groupId invitedByGroupMe ) pure $ Right incognitoLdn -createGroupInvitedViaLink :: DB.Connection -> VersionRange -> User -> Connection -> GroupLinkInvitation -> ExceptT StoreError IO (GroupInfo, GroupMember) +createGroupInvitedViaLink :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Connection -> GroupLinkInvitation -> ExceptT StoreError IO (GroupInfo, GroupMember) createGroupInvitedViaLink db vr @@ -492,7 +502,7 @@ createGroupInvitedViaLink -- using IBUnknown since host is created without contact void $ createContactMemberInv_ db user groupId (Just hostMemberId) user invitedMember GCUserMember GSMemAccepted IBUnknown customUserProfileId currentTs vr liftIO $ setViaGroupLinkHash db groupId connId - (,) <$> getGroupInfo db vr user groupId <*> getGroupMemberById db user hostMemberId + (,) <$> getGroupInfo db vr user groupId <*> getGroupMemberById db vr user hostMemberId where insertGroup_ currentTs = ExceptT $ do let GroupProfile {displayName, fullName, description, image, groupPreferences} = groupProfile @@ -549,10 +559,10 @@ setGroupInvitationChatItemId db User {userId} groupId chatItemId = do -- TODO return the last connection that is ready, not any last connection -- requires updating connection status -getGroup :: DB.Connection -> VersionRange -> User -> GroupId -> ExceptT StoreError IO Group +getGroup :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupId -> ExceptT StoreError IO Group getGroup db vr user groupId = do gInfo <- getGroupInfo db vr user groupId - members <- liftIO $ getGroupMembers db user gInfo + members <- liftIO $ getGroupMembers db vr user gInfo pure $ Group gInfo members deleteGroupConnectionsAndFiles :: DB.Connection -> User -> GroupInfo -> [GroupMember] -> IO () @@ -604,12 +614,12 @@ deleteGroupProfile_ db userId groupId = |] (userId, groupId) -getUserGroups :: DB.Connection -> VersionRange -> User -> IO [Group] +getUserGroups :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> IO [Group] getUserGroups db vr user@User {userId} = do groupIds <- map fromOnly <$> DB.query db "SELECT group_id FROM groups WHERE user_id = ?" (Only userId) rights <$> mapM (runExceptT . getGroup db vr user) groupIds -getUserGroupDetails :: DB.Connection -> VersionRange -> User -> Maybe ContactId -> Maybe String -> IO [GroupInfo] +getUserGroupDetails :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Maybe ContactId -> Maybe String -> IO [GroupInfo] getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = map (toGroupInfo vr userContactId) <$> DB.query @@ -632,7 +642,7 @@ getUserGroupDetails db vr User {userId, userContactId} _contactId_ search_ = where search = fromMaybe "" search_ -getUserGroupsWithSummary :: DB.Connection -> VersionRange -> User -> Maybe ContactId -> Maybe String -> IO [(GroupInfo, GroupSummary)] +getUserGroupsWithSummary :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Maybe ContactId -> Maybe String -> IO [(GroupInfo, GroupSummary)] getUserGroupsWithSummary db vr user _contactId_ search_ = getUserGroupDetails db vr user _contactId_ search_ >>= mapM (\g@GroupInfo {groupId} -> (g,) <$> getGroupSummary db user groupId) @@ -673,7 +683,7 @@ checkContactHasGroups :: DB.Connection -> User -> Contact -> IO (Maybe GroupId) checkContactHasGroups db User {userId} Contact {contactId} = maybeFirstRow fromOnly $ DB.query db "SELECT group_id FROM group_members WHERE user_id = ? AND contact_id = ? LIMIT 1" (userId, contactId) -getGroupInfoByName :: DB.Connection -> VersionRange -> User -> GroupName -> ExceptT StoreError IO GroupInfo +getGroupInfoByName :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupName -> ExceptT StoreError IO GroupInfo getGroupInfoByName db vr user gName = do gId <- getGroupIdByName db user gName getGroupInfo db vr user gId @@ -685,8 +695,9 @@ groupMemberQuery = m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter, - c.peer_chat_min_version, c.peer_chat_max_version + c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, + c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version FROM group_members m JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) LEFT JOIN connections c ON c.connection_id = ( @@ -696,41 +707,41 @@ groupMemberQuery = ) |] -getGroupMember :: DB.Connection -> User -> GroupId -> GroupMemberId -> ExceptT StoreError IO GroupMember -getGroupMember db user@User {userId} groupId groupMemberId = - ExceptT . firstRow (toContactMember user) (SEGroupMemberNotFound groupMemberId) $ +getGroupMember :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupId -> GroupMemberId -> ExceptT StoreError IO GroupMember +getGroupMember db vr user@User {userId} groupId groupMemberId = + ExceptT . firstRow (toContactMember vr user) (SEGroupMemberNotFound groupMemberId) $ DB.query db (groupMemberQuery <> " WHERE m.group_id = ? AND m.group_member_id = ? AND m.user_id = ?") (userId, groupId, groupMemberId, userId) -getGroupMemberById :: DB.Connection -> User -> GroupMemberId -> ExceptT StoreError IO GroupMember -getGroupMemberById db user@User {userId} groupMemberId = - ExceptT . firstRow (toContactMember user) (SEGroupMemberNotFound groupMemberId) $ +getGroupMemberById :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupMemberId -> ExceptT StoreError IO GroupMember +getGroupMemberById db vr user@User {userId} groupMemberId = + ExceptT . firstRow (toContactMember vr user) (SEGroupMemberNotFound groupMemberId) $ DB.query db (groupMemberQuery <> " WHERE m.group_member_id = ? AND m.user_id = ?") (userId, groupMemberId, userId) -getGroupMemberByMemberId :: DB.Connection -> User -> GroupInfo -> MemberId -> ExceptT StoreError IO GroupMember -getGroupMemberByMemberId db user@User {userId} GroupInfo {groupId} memberId = - ExceptT . firstRow (toContactMember user) (SEGroupMemberNotFoundByMemberId memberId) $ +getGroupMemberByMemberId :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupInfo -> MemberId -> ExceptT StoreError IO GroupMember +getGroupMemberByMemberId db vr user@User {userId} GroupInfo {groupId} memberId = + ExceptT . firstRow (toContactMember vr user) (SEGroupMemberNotFoundByMemberId memberId) $ DB.query db (groupMemberQuery <> " WHERE m.group_id = ? AND m.member_id = ?") (userId, groupId, memberId) -getGroupMembers :: DB.Connection -> User -> GroupInfo -> IO [GroupMember] -getGroupMembers db user@User {userId, userContactId} GroupInfo {groupId} = do - map (toContactMember user) +getGroupMembers :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupInfo -> IO [GroupMember] +getGroupMembers db vr user@User {userId, userContactId} GroupInfo {groupId} = do + map (toContactMember vr user) <$> DB.query db (groupMemberQuery <> " WHERE m.group_id = ? AND m.user_id = ? AND (m.contact_id IS NULL OR m.contact_id != ?)") (userId, groupId, userId, userContactId) -getGroupMembersForExpiration :: DB.Connection -> User -> GroupInfo -> IO [GroupMember] -getGroupMembersForExpiration db user@User {userId, userContactId} GroupInfo {groupId} = do - map (toContactMember user) +getGroupMembersForExpiration :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupInfo -> IO [GroupMember] +getGroupMembersForExpiration db vr user@User {userId, userContactId} GroupInfo {groupId} = do + map (toContactMember vr user) <$> DB.query db ( groupMemberQuery @@ -744,9 +755,9 @@ getGroupMembersForExpiration db user@User {userId, userContactId} GroupInfo {gro ) (userId, groupId, userId, userContactId, GSMemRemoved, GSMemLeft, GSMemGroupDeleted, GSMemUnknown) -toContactMember :: User -> (GroupMemberRow :. MaybeConnectionRow) -> GroupMember -toContactMember User {userContactId} (memberRow :. connRow) = - (toGroupMember userContactId memberRow) {activeConn = toMaybeConnection connRow} +toContactMember :: (PQSupport -> VersionRangeChat) -> User -> (GroupMemberRow :. MaybeConnectionRow) -> GroupMember +toContactMember vr User {userContactId} (memberRow :. connRow) = + (toGroupMember userContactId memberRow) {activeConn = toMaybeConnection vr connRow} getGroupCurrentMembersCount :: DB.Connection -> User -> GroupInfo -> IO Int getGroupCurrentMembersCount db User {userId} GroupInfo {groupId} = do @@ -762,14 +773,14 @@ getGroupCurrentMembersCount db User {userId} GroupInfo {groupId} = do (groupId, userId) pure $ length $ filter memberCurrent' statuses -getGroupInvitation :: DB.Connection -> VersionRange -> User -> GroupId -> ExceptT StoreError IO ReceivedGroupInvitation +getGroupInvitation :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupId -> ExceptT StoreError IO ReceivedGroupInvitation getGroupInvitation db vr user groupId = getConnRec_ user >>= \case Just connRequest -> do groupInfo@GroupInfo {membership} <- getGroupInfo db vr user groupId when (memberStatus membership /= GSMemInvited) $ throwError SEGroupAlreadyJoined hostId <- getHostMemberId_ db user groupId - fromMember <- getGroupMember db user groupId hostId + fromMember <- getGroupMember db vr user groupId hostId pure ReceivedGroupInvitation {fromMember, connRequest, groupInfo} _ -> throwError SEGroupInvitationNotFound where @@ -780,14 +791,14 @@ getGroupInvitation db vr user groupId = createNewContactMember :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> Contact -> GroupMemberRole -> ConnId -> ConnReqInvitation -> SubscriptionMode -> ExceptT StoreError IO GroupMember createNewContactMember _ _ _ _ Contact {localDisplayName, activeConn = Nothing} _ _ _ _ = throwError $ SEContactNotReady localDisplayName -createNewContactMember db gVar User {userId, userContactId} GroupInfo {groupId, membership} Contact {contactId, localDisplayName, profile, activeConn = Just Connection {peerChatVRange}} memberRole agentConnId connRequest subMode = +createNewContactMember db gVar User {userId, userContactId} GroupInfo {groupId, membership} Contact {contactId, localDisplayName, profile, activeConn = Just Connection {connChatVersion, peerChatVRange}} memberRole agentConnId connRequest subMode = createWithRandomId gVar $ \memId -> do createdAt <- liftIO getCurrentTime member@GroupMember {groupMemberId} <- createMember_ (MemberId memId) createdAt - void $ createMemberConnection_ db userId groupMemberId agentConnId (fromJVersionRange peerChatVRange) Nothing 0 createdAt subMode + void $ createMemberConnection_ db userId groupMemberId agentConnId connChatVersion peerChatVRange Nothing 0 createdAt subMode pure member where - JVersionRange (VersionRange minV maxV) = peerChatVRange + VersionRange minV maxV = peerChatVRange invitedByGroupMemberId = groupMemberId' membership createMember_ memberId createdAt = do insertMember_ @@ -827,13 +838,13 @@ createNewContactMember db gVar User {userId, userContactId} GroupInfo {groupId, :. (minV, maxV) ) -createNewContactMemberAsync :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> Contact -> GroupMemberRole -> (CommandId, ConnId) -> VersionRange -> SubscriptionMode -> ExceptT StoreError IO () -createNewContactMemberAsync db gVar user@User {userId, userContactId} GroupInfo {groupId, membership} Contact {contactId, localDisplayName, profile} memberRole (cmdId, agentConnId) peerChatVRange subMode = +createNewContactMemberAsync :: DB.Connection -> TVar ChaChaDRG -> User -> GroupInfo -> Contact -> GroupMemberRole -> (CommandId, ConnId) -> VersionChat -> VersionRangeChat -> SubscriptionMode -> ExceptT StoreError IO () +createNewContactMemberAsync db gVar user@User {userId, userContactId} GroupInfo {groupId, membership} Contact {contactId, localDisplayName, profile} memberRole (cmdId, agentConnId) chatV peerChatVRange subMode = createWithRandomId gVar $ \memId -> do createdAt <- liftIO getCurrentTime insertMember_ (MemberId memId) createdAt groupMemberId <- liftIO $ insertedRowId db - Connection {connId} <- createMemberConnection_ db userId groupMemberId agentConnId peerChatVRange Nothing 0 createdAt subMode + Connection {connId} <- createMemberConnection_ db userId groupMemberId agentConnId chatV peerChatVRange Nothing 0 createdAt subMode setCommandConnId db user cmdId connId where VersionRange minV maxV = peerChatVRange @@ -868,7 +879,7 @@ createAcceptedMember groupMemberId <- liftIO $ insertedRowId db pure (groupMemberId, MemberId memId) where - JVersionRange (VersionRange minV maxV) = cReqChatVRange + VersionRange minV maxV = cReqChatVRange insertMember_ memberId createdAt = DB.execute db @@ -884,20 +895,21 @@ createAcceptedMember :. (minV, maxV) ) -createAcceptedMemberConnection :: DB.Connection -> User -> (CommandId, ConnId) -> UserContactRequest -> GroupMemberId -> SubscriptionMode -> IO () +createAcceptedMemberConnection :: DB.Connection -> User -> (CommandId, ConnId) -> VersionChat -> UserContactRequest -> GroupMemberId -> SubscriptionMode -> IO () createAcceptedMemberConnection db user@User {userId} (cmdId, agentConnId) + chatV UserContactRequest {cReqChatVRange, userContactLinkId} groupMemberId subMode = do createdAt <- liftIO getCurrentTime - Connection {connId} <- createConnection_ db userId ConnMember (Just groupMemberId) agentConnId (fromJVersionRange cReqChatVRange) Nothing (Just userContactLinkId) Nothing 0 createdAt subMode + Connection {connId} <- createConnection_ db userId ConnMember (Just groupMemberId) agentConnId chatV cReqChatVRange Nothing (Just userContactLinkId) Nothing 0 createdAt subMode PQSupportOff setCommandConnId db user cmdId connId -getContactViaMember :: DB.Connection -> User -> GroupMember -> ExceptT StoreError IO Contact -getContactViaMember db user@User {userId} GroupMember {groupMemberId} = do +getContactViaMember :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupMember -> ExceptT StoreError IO Contact +getContactViaMember db vr user@User {userId} GroupMember {groupMemberId} = do contactId <- ExceptT $ firstRow fromOnly (SEContactNotFoundByMemberId groupMemberId) $ @@ -911,7 +923,7 @@ getContactViaMember db user@User {userId} GroupMember {groupMemberId} = do LIMIT 1 |] (userId, groupMemberId) - getContact db user contactId + getContact db vr user contactId setNewContactMemberConnRequest :: DB.Connection -> User -> GroupMember -> ConnReqInvitation -> IO () setNewContactMemberConnRequest db User {userId} GroupMember {groupMemberId} connRequest = do @@ -923,15 +935,15 @@ getMemberInvitation db User {userId} groupMemberId = fmap join . maybeFirstRow fromOnly $ DB.query db "SELECT sent_inv_queue_info FROM group_members WHERE group_member_id = ? AND user_id = ?" (groupMemberId, userId) -createMemberConnection :: DB.Connection -> UserId -> GroupMember -> ConnId -> VersionRange -> SubscriptionMode -> IO () -createMemberConnection db userId GroupMember {groupMemberId} agentConnId peerChatVRange subMode = do +createMemberConnection :: DB.Connection -> UserId -> GroupMember -> ConnId -> VersionChat -> VersionRangeChat -> SubscriptionMode -> IO () +createMemberConnection db userId GroupMember {groupMemberId} agentConnId chatV peerChatVRange subMode = do currentTs <- getCurrentTime - void $ createMemberConnection_ db userId groupMemberId agentConnId peerChatVRange Nothing 0 currentTs subMode + void $ createMemberConnection_ db userId groupMemberId agentConnId chatV peerChatVRange Nothing 0 currentTs subMode -createMemberConnectionAsync :: DB.Connection -> User -> GroupMemberId -> (CommandId, ConnId) -> VersionRange -> SubscriptionMode -> IO () -createMemberConnectionAsync db user@User {userId} groupMemberId (cmdId, agentConnId) peerChatVRange subMode = do +createMemberConnectionAsync :: DB.Connection -> User -> GroupMemberId -> (CommandId, ConnId) -> VersionChat -> VersionRangeChat -> SubscriptionMode -> IO () +createMemberConnectionAsync db user@User {userId} groupMemberId (cmdId, agentConnId) chatV peerChatVRange subMode = do currentTs <- getCurrentTime - Connection {connId} <- createMemberConnection_ db userId groupMemberId agentConnId peerChatVRange Nothing 0 currentTs subMode + Connection {connId} <- createMemberConnection_ db userId groupMemberId agentConnId chatV peerChatVRange Nothing 0 currentTs subMode setCommandConnId db user cmdId connId updateGroupMemberStatus :: DB.Connection -> UserId -> GroupMember -> GroupMemberStatus -> IO () @@ -997,7 +1009,7 @@ createNewMember_ createdAt = do let invitedById = fromInvitedBy userContactId invitedBy activeConn = Nothing - mcvr@(VersionRange minV maxV) = maybe chatInitialVRange fromChatVRange memChatVRange + memberChatVRange@(VersionRange minV maxV) = maybe chatInitialVRange fromChatVRange memChatVRange DB.execute db [sql| @@ -1029,7 +1041,7 @@ createNewMember_ memberContactId, memberContactProfileId, activeConn, - memberChatVRange = JVersionRange mcvr + memberChatVRange } checkGroupMemberHasItems :: DB.Connection -> User -> GroupMember -> IO (Maybe ChatItemId) @@ -1062,7 +1074,7 @@ updateGroupMemberRole :: DB.Connection -> User -> GroupMember -> GroupMemberRole updateGroupMemberRole db User {userId} GroupMember {groupMemberId} memRole = DB.execute db "UPDATE group_members SET member_role = ? WHERE user_id = ? AND group_member_id = ?" (memRole, userId, groupMemberId) -createIntroductions :: DB.Connection -> Version -> [GroupMember] -> GroupMember -> IO [GroupMemberIntro] +createIntroductions :: DB.Connection -> VersionChat -> [GroupMember] -> GroupMember -> IO [GroupMemberIntro] createIntroductions db chatV members toMember = do let reMembers = filter (\m -> memberCurrent m && groupMemberId' m /= groupMemberId' toMember) members if null reMembers @@ -1157,10 +1169,10 @@ getIntroduction db reMember toMember = ExceptT $ do in Right GroupMemberIntro {introId, reMember, toMember, introStatus, introInvitation} toIntro _ = Left SEIntroNotFound -getForwardIntroducedMembers :: DB.Connection -> User -> GroupMember -> Bool -> IO [GroupMember] -getForwardIntroducedMembers db user invitee highlyAvailable = do +getForwardIntroducedMembers :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupMember -> Bool -> IO [GroupMember] +getForwardIntroducedMembers db vr user invitee highlyAvailable = do memberIds <- map fromOnly <$> query - filter memberCurrent . rights <$> mapM (runExceptT . getGroupMemberById db user) memberIds + filter memberCurrent . rights <$> mapM (runExceptT . getGroupMemberById db vr user) memberIds where mId = groupMemberId' invitee query @@ -1169,7 +1181,7 @@ getForwardIntroducedMembers db user invitee highlyAvailable = do DB.query db (q <> " AND intro_chat_protocol_version >= ?") - (mId, GMIntroReConnected, GMIntroToConnected, GMIntroConnected, minVersion groupForwardVRange) + (mId, GMIntroReConnected, GMIntroToConnected, GMIntroConnected, groupForwardVersion) q = [sql| SELECT re_group_member_id @@ -1177,10 +1189,10 @@ getForwardIntroducedMembers db user invitee highlyAvailable = do WHERE to_group_member_id = ? AND intro_status NOT IN (?,?,?) |] -getForwardInvitedMembers :: DB.Connection -> User -> GroupMember -> Bool -> IO [GroupMember] -getForwardInvitedMembers db user forwardMember highlyAvailable = do +getForwardInvitedMembers :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupMember -> Bool -> IO [GroupMember] +getForwardInvitedMembers db vr user forwardMember highlyAvailable = do memberIds <- map fromOnly <$> query - filter memberCurrent . rights <$> mapM (runExceptT . getGroupMemberById db user) memberIds + filter memberCurrent . rights <$> mapM (runExceptT . getGroupMemberById db vr user) memberIds where mId = groupMemberId' forwardMember query @@ -1189,7 +1201,7 @@ getForwardInvitedMembers db user forwardMember highlyAvailable = do DB.query db (q <> " AND intro_chat_protocol_version >= ?") - (mId, GMIntroReConnected, GMIntroToConnected, GMIntroConnected, minVersion groupForwardVRange) + (mId, GMIntroReConnected, GMIntroToConnected, GMIntroConnected, groupForwardVersion) q = [sql| SELECT to_group_member_id @@ -1197,12 +1209,13 @@ getForwardInvitedMembers db user forwardMember highlyAvailable = do WHERE re_group_member_id = ? AND intro_status NOT IN (?,?,?) |] -createIntroReMember :: DB.Connection -> User -> GroupInfo -> GroupMember -> MemberInfo -> Maybe MemberRestrictions -> (CommandId, ConnId) -> Maybe (CommandId, ConnId) -> Maybe ProfileId -> SubscriptionMode -> ExceptT StoreError IO GroupMember +createIntroReMember :: DB.Connection -> User -> GroupInfo -> GroupMember -> VersionChat -> MemberInfo -> Maybe MemberRestrictions -> (CommandId, ConnId) -> Maybe (CommandId, ConnId) -> Maybe ProfileId -> SubscriptionMode -> ExceptT StoreError IO GroupMember createIntroReMember db user@User {userId} gInfo@GroupInfo {groupId} _host@GroupMember {memberContactId, activeConn} + chatV memInfo@(MemberInfo _ _ memChatVRange memberProfile) memRestrictions_ (groupCmdId, groupAgentConnId) @@ -1215,7 +1228,7 @@ createIntroReMember currentTs <- liftIO getCurrentTime newMember <- case directConnIds of Just (directCmdId, directAgentConnId) -> do - Connection {connId = directConnId} <- liftIO $ createConnection_ db userId ConnContact Nothing directAgentConnId mcvr memberContactId Nothing customUserProfileId cLevel currentTs subMode + Connection {connId = directConnId} <- liftIO $ createConnection_ db userId ConnContact Nothing directAgentConnId chatV mcvr memberContactId Nothing customUserProfileId cLevel currentTs subMode PQSupportOff liftIO $ setCommandConnId db user directCmdId directConnId (localDisplayName, contactId, memProfileId) <- createContact_ db userId memberProfile "" (Just groupId) currentTs False liftIO $ DB.execute db "UPDATE connections SET contact_id = ?, updated_at = ? WHERE connection_id = ?" (contactId, currentTs, directConnId) @@ -1225,18 +1238,18 @@ createIntroReMember pure $ NewGroupMember {memInfo, memCategory = GCPreMember, memStatus = GSMemIntroduced, memRestriction, memInvitedBy = IBUnknown, memInvitedByGroupMemberId = Nothing, localDisplayName, memContactId = Nothing, memProfileId} liftIO $ do member <- createNewMember_ db user gInfo newMember currentTs - conn@Connection {connId = groupConnId} <- createMemberConnection_ db userId (groupMemberId' member) groupAgentConnId mcvr memberContactId cLevel currentTs subMode + conn@Connection {connId = groupConnId} <- createMemberConnection_ db userId (groupMemberId' member) groupAgentConnId chatV mcvr memberContactId cLevel currentTs subMode liftIO $ setCommandConnId db user groupCmdId groupConnId pure (member :: GroupMember) {activeConn = Just conn} -createIntroToMemberContact :: DB.Connection -> User -> GroupMember -> GroupMember -> VersionRange -> (CommandId, ConnId) -> Maybe (CommandId, ConnId) -> Maybe ProfileId -> SubscriptionMode -> IO () -createIntroToMemberContact db user@User {userId} GroupMember {memberContactId = viaContactId, activeConn} _to@GroupMember {groupMemberId, localDisplayName} mcvr (groupCmdId, groupAgentConnId) directConnIds customUserProfileId subMode = do +createIntroToMemberContact :: DB.Connection -> User -> GroupMember -> GroupMember -> VersionChat -> VersionRangeChat -> (CommandId, ConnId) -> Maybe (CommandId, ConnId) -> Maybe ProfileId -> SubscriptionMode -> IO () +createIntroToMemberContact db user@User {userId} GroupMember {memberContactId = viaContactId, activeConn} _to@GroupMember {groupMemberId, localDisplayName} chatV mcvr (groupCmdId, groupAgentConnId) directConnIds customUserProfileId subMode = do let cLevel = 1 + maybe 0 (\Connection {connLevel} -> connLevel) activeConn currentTs <- getCurrentTime - Connection {connId = groupConnId} <- createMemberConnection_ db userId groupMemberId groupAgentConnId mcvr viaContactId cLevel currentTs subMode + Connection {connId = groupConnId} <- createMemberConnection_ db userId groupMemberId groupAgentConnId chatV mcvr viaContactId cLevel currentTs subMode setCommandConnId db user groupCmdId groupConnId forM_ directConnIds $ \(directCmdId, directAgentConnId) -> do - Connection {connId = directConnId} <- createConnection_ db userId ConnContact Nothing directAgentConnId mcvr viaContactId Nothing customUserProfileId cLevel currentTs subMode + Connection {connId = directConnId} <- createConnection_ db userId ConnContact Nothing directAgentConnId chatV mcvr viaContactId Nothing customUserProfileId cLevel currentTs subMode PQSupportOff setCommandConnId db user directCmdId directConnId contactId <- createMemberContact_ directConnId currentTs updateMember_ contactId currentTs @@ -1266,10 +1279,11 @@ createIntroToMemberContact db user@User {userId} GroupMember {memberContactId = |] [":contact_id" := contactId, ":updated_at" := ts, ":group_member_id" := groupMemberId] -createMemberConnection_ :: DB.Connection -> UserId -> Int64 -> ConnId -> VersionRange -> Maybe Int64 -> Int -> UTCTime -> SubscriptionMode -> IO Connection -createMemberConnection_ db userId groupMemberId agentConnId peerChatVRange viaContact = createConnection_ db userId ConnMember (Just groupMemberId) agentConnId peerChatVRange viaContact Nothing Nothing +createMemberConnection_ :: DB.Connection -> UserId -> Int64 -> ConnId -> VersionChat -> VersionRangeChat -> Maybe Int64 -> Int -> UTCTime -> SubscriptionMode -> IO Connection +createMemberConnection_ db userId groupMemberId agentConnId chatV peerChatVRange viaContact connLevel currentTs subMode = + createConnection_ db userId ConnMember (Just groupMemberId) agentConnId chatV peerChatVRange viaContact Nothing Nothing connLevel currentTs subMode PQSupportOff -getViaGroupMember :: DB.Connection -> VersionRange -> User -> Contact -> IO (Maybe (GroupInfo, GroupMember)) +getViaGroupMember :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Contact -> IO (Maybe (GroupInfo, GroupMember)) getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = maybeFirstRow toGroupAndMember $ DB.query @@ -1289,8 +1303,9 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = m.group_member_id, m.group_id, m.member_id, m.peer_chat_min_version, m.peer_chat_max_version, m.member_role, m.member_category, m.member_status, m.show_messages, m.member_restriction, m.invited_by, m.invited_by_group_member_id, m.local_display_name, m.contact_id, m.contact_profile_id, p.contact_profile_id, p.display_name, p.full_name, p.image, p.contact_link, p.local_alias, p.preferences, c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter, - c.peer_chat_min_version, c.peer_chat_max_version + c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, + c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version FROM group_members m JOIN contacts ct ON ct.contact_id = m.contact_id JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) @@ -1311,10 +1326,10 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = toGroupAndMember (groupInfoRow :. memberRow :. connRow) = let groupInfo = toGroupInfo vr userContactId groupInfoRow member = toGroupMember userContactId memberRow - in (groupInfo, (member :: GroupMember) {activeConn = toMaybeConnection connRow}) + in (groupInfo, (member :: GroupMember) {activeConn = toMaybeConnection vr connRow}) -getViaGroupContact :: DB.Connection -> User -> GroupMember -> IO (Maybe Contact) -getViaGroupContact db user@User {userId} GroupMember {groupMemberId} = do +getViaGroupContact :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupMember -> IO (Maybe Contact) +getViaGroupContact db vr user@User {userId} GroupMember {groupMemberId} = do contactId_ <- maybeFirstRow fromOnly $ DB.query @@ -1328,7 +1343,7 @@ getViaGroupContact db user@User {userId} GroupMember {groupMemberId} = do LIMIT 1 |] (userId, groupMemberId) - maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getContact db user) contactId_ + maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getContact db vr user) contactId_ updateGroupProfile :: DB.Connection -> User -> GroupInfo -> GroupProfile -> ExceptT StoreError IO GroupInfo updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, groupProfile = GroupProfile {displayName}} p'@GroupProfile {displayName = newName, fullName, description, image, groupPreferences} @@ -1364,7 +1379,7 @@ updateGroupProfile db user@User {userId} g@GroupInfo {groupId, localDisplayName, (ldn, currentTs, userId, groupId) safeDeleteLDN db user localDisplayName -getGroupInfo :: DB.Connection -> VersionRange -> User -> Int64 -> ExceptT StoreError IO GroupInfo +getGroupInfo :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Int64 -> ExceptT StoreError IO GroupInfo getGroupInfo db vr User {userId, userContactId} groupId = ExceptT . firstRow (toGroupInfo vr userContactId) (SEGroupNotFound groupId) $ DB.query @@ -1387,7 +1402,7 @@ getGroupInfo db vr User {userId, userContactId} groupId = |] (groupId, userId, userContactId) -getGroupInfoByUserContactLinkConnReq :: DB.Connection -> VersionRange -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe GroupInfo) +getGroupInfoByUserContactLinkConnReq :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe GroupInfo) getGroupInfoByUserContactLinkConnReq db vr user@User {userId} (cReqSchema1, cReqSchema2) = do groupId_ <- maybeFirstRow fromOnly $ @@ -1401,7 +1416,7 @@ getGroupInfoByUserContactLinkConnReq db vr user@User {userId} (cReqSchema1, cReq (userId, cReqSchema1, cReqSchema2) maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getGroupInfo db vr user) groupId_ -getGroupInfoByGroupLinkHash :: DB.Connection -> VersionRange -> User -> (ConnReqUriHash, ConnReqUriHash) -> IO (Maybe GroupInfo) +getGroupInfoByGroupLinkHash :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> (ConnReqUriHash, ConnReqUriHash) -> IO (Maybe GroupInfo) getGroupInfoByGroupLinkHash db vr user@User {userId, userContactId} (groupLinkHash1, groupLinkHash2) = do groupId_ <- maybeFirstRow fromOnly $ @@ -1428,7 +1443,7 @@ getGroupMemberIdByName db User {userId} groupId groupMemberName = ExceptT . firstRow fromOnly (SEGroupMemberNameNotFound groupId groupMemberName) $ DB.query db "SELECT group_member_id FROM group_members WHERE user_id = ? AND group_id = ? AND local_display_name = ?" (userId, groupId, groupMemberName) -getActiveMembersByName :: DB.Connection -> VersionRange -> User -> ContactName -> ExceptT StoreError IO [(GroupInfo, GroupMember)] +getActiveMembersByName :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ContactName -> ExceptT StoreError IO [(GroupInfo, GroupMember)] getActiveMembersByName db vr user@User {userId} groupMemberName = do groupMemberIds :: [(GroupId, GroupMemberId)] <- liftIO $ @@ -1443,19 +1458,19 @@ getActiveMembersByName db vr user@User {userId} groupMemberName = do (userId, groupMemberName, GSMemConnected, GSMemComplete, GCUserMember) possibleMembers <- forM groupMemberIds $ \(groupId, groupMemberId) -> do groupInfo <- getGroupInfo db vr user groupId - groupMember <- getGroupMember db user groupId groupMemberId + groupMember <- getGroupMember db vr user groupId groupMemberId pure (groupInfo, groupMember) pure $ sortOn (Down . ts . fst) possibleMembers where ts GroupInfo {chatTs, updatedAt} = fromMaybe updatedAt chatTs -getMatchingContacts :: DB.Connection -> User -> Contact -> IO [Contact] -getMatchingContacts db user@User {userId} Contact {contactId, profile = LocalProfile {displayName, fullName, image}} = do +getMatchingContacts :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Contact -> IO [Contact] +getMatchingContacts db vr user@User {userId} Contact {contactId, profile = LocalProfile {displayName, fullName, image}} = do contactIds <- map fromOnly <$> case image of Just img -> DB.query db (q <> " AND p.image = ?") (userId, contactId, CSActive, displayName, fullName, img) Nothing -> DB.query db (q <> " AND p.image is NULL") (userId, contactId, CSActive, displayName, fullName) - rights <$> mapM (runExceptT . getContact db user) contactIds + rights <$> mapM (runExceptT . getContact db vr user) contactIds where -- this query is different from one in getMatchingMemberContacts -- it checks that it's not the same contact @@ -1469,13 +1484,13 @@ getMatchingContacts db user@User {userId} Contact {contactId, profile = LocalPro AND p.display_name = ? AND p.full_name = ? |] -getMatchingMembers :: DB.Connection -> User -> Contact -> IO [GroupMember] -getMatchingMembers db user@User {userId} Contact {profile = LocalProfile {displayName, fullName, image}} = do +getMatchingMembers :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Contact -> IO [GroupMember] +getMatchingMembers db vr user@User {userId} Contact {profile = LocalProfile {displayName, fullName, image}} = do memberIds <- map fromOnly <$> case image of Just img -> DB.query db (q <> " AND p.image = ?") (userId, GCUserMember, displayName, fullName, img) Nothing -> DB.query db (q <> " AND p.image is NULL") (userId, GCUserMember, displayName, fullName) - filter memberCurrent . rights <$> mapM (runExceptT . getGroupMemberById db user) memberIds + filter memberCurrent . rights <$> mapM (runExceptT . getGroupMemberById db vr user) memberIds where -- only match with members without associated contact q = @@ -1488,14 +1503,14 @@ getMatchingMembers db user@User {userId} Contact {profile = LocalProfile {displa AND p.display_name = ? AND p.full_name = ? |] -getMatchingMemberContacts :: DB.Connection -> User -> GroupMember -> IO [Contact] -getMatchingMemberContacts _ _ GroupMember {memberContactId = Just _} = pure [] -getMatchingMemberContacts db user@User {userId} GroupMember {memberProfile = LocalProfile {displayName, fullName, image}} = do +getMatchingMemberContacts :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupMember -> IO [Contact] +getMatchingMemberContacts _ _ _ GroupMember {memberContactId = Just _} = pure [] +getMatchingMemberContacts db vr user@User {userId} GroupMember {memberProfile = LocalProfile {displayName, fullName, image}} = do contactIds <- map fromOnly <$> case image of Just img -> DB.query db (q <> " AND p.image = ?") (userId, CSActive, displayName, fullName, img) Nothing -> DB.query db (q <> " AND p.image is NULL") (userId, CSActive, displayName, fullName) - rights <$> mapM (runExceptT . getContact db user) contactIds + rights <$> mapM (runExceptT . getContact db vr user) contactIds where q = [sql| @@ -1527,8 +1542,8 @@ createSentProbeHash db userId probeId to = do "INSERT INTO sent_probe_hashes (sent_probe_id, contact_id, group_member_id, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?)" (probeId, ctId, gmId, userId, currentTs, currentTs) -matchReceivedProbe :: DB.Connection -> User -> ContactOrMember -> Probe -> IO [ContactOrMember] -matchReceivedProbe db user@User {userId} from (Probe probe) = do +matchReceivedProbe :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ContactOrMember -> Probe -> IO [ContactOrMember] +matchReceivedProbe db vr user@User {userId} from (Probe probe) = do let probeHash = C.sha256Hash probe cgmIds <- DB.query @@ -1549,7 +1564,7 @@ matchReceivedProbe db user@User {userId} from (Probe probe) = do "INSERT INTO received_probes (contact_id, group_member_id, probe, probe_hash, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?,?)" (ctId, gmId, probe, probeHash, userId, currentTs, currentTs) let cgmIds' = filterFirstContactId cgmIds - catMaybes <$> mapM (getContactOrMember_ db user) cgmIds' + catMaybes <$> mapM (getContactOrMember_ db vr user) cgmIds' where filterFirstContactId :: [(Maybe ContactId, Maybe GroupId, Maybe GroupMemberId)] -> [(Maybe ContactId, Maybe GroupId, Maybe GroupMemberId)] filterFirstContactId cgmIds = do @@ -1559,8 +1574,8 @@ matchReceivedProbe db user@User {userId} from (Probe probe) = do (x : _) -> [x] ctIds' <> memIds -matchReceivedProbeHash :: DB.Connection -> User -> ContactOrMember -> ProbeHash -> IO (Maybe (ContactOrMember, Probe)) -matchReceivedProbeHash db user@User {userId} from (ProbeHash probeHash) = do +matchReceivedProbeHash :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ContactOrMember -> ProbeHash -> IO (Maybe (ContactOrMember, Probe)) +matchReceivedProbeHash db vr user@User {userId} from (ProbeHash probeHash) = do probeIds <- maybeFirstRow id $ DB.query @@ -1580,11 +1595,11 @@ matchReceivedProbeHash db user@User {userId} from (ProbeHash probeHash) = do db "INSERT INTO received_probes (contact_id, group_member_id, probe_hash, user_id, created_at, updated_at) VALUES (?,?,?,?,?,?)" (ctId, gmId, probeHash, userId, currentTs, currentTs) - pure probeIds $>>= \(Only probe :. cgmIds) -> (,Probe probe) <$$> getContactOrMember_ db user cgmIds + pure probeIds $>>= \(Only probe :. cgmIds) -> (,Probe probe) <$$> getContactOrMember_ db vr user cgmIds -matchSentProbe :: DB.Connection -> User -> ContactOrMember -> Probe -> IO (Maybe ContactOrMember) -matchSentProbe db user@User {userId} _from (Probe probe) = do - cgmIds $>>= getContactOrMember_ db user +matchSentProbe :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ContactOrMember -> Probe -> IO (Maybe ContactOrMember) +matchSentProbe db vr user@User {userId} _from (Probe probe) = do + cgmIds $>>= getContactOrMember_ db vr user where (ctId, gmId) = contactOrMemberIds _from cgmIds = @@ -1603,16 +1618,16 @@ matchSentProbe db user@User {userId} _from (Probe probe) = do |] (userId, probe, ctId, gmId) -getContactOrMember_ :: DB.Connection -> User -> (Maybe ContactId, Maybe GroupId, Maybe GroupMemberId) -> IO (Maybe ContactOrMember) -getContactOrMember_ db user ids = +getContactOrMember_ :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> (Maybe ContactId, Maybe GroupId, Maybe GroupMemberId) -> IO (Maybe ContactOrMember) +getContactOrMember_ db vr user ids = fmap eitherToMaybe . runExceptT $ case ids of - (Just ctId, _, _) -> COMContact <$> getContact db user ctId - (_, Just gId, Just gmId) -> COMGroupMember <$> getGroupMember db user gId gmId + (Just ctId, _, _) -> COMContact <$> getContact db vr user ctId + (_, Just gId, Just gmId) -> COMGroupMember <$> getGroupMember db vr user gId gmId _ -> throwError $ SEInternalError "" -- if requested merge direction is overruled (toFromContacts), keepLDN is kept -mergeContactRecords :: DB.Connection -> User -> Contact -> Contact -> ExceptT StoreError IO Contact -mergeContactRecords db user@User {userId} to@Contact {localDisplayName = keepLDN} from = do +mergeContactRecords :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Contact -> Contact -> ExceptT StoreError IO Contact +mergeContactRecords db vr user@User {userId} to@Contact {localDisplayName = keepLDN} from = do let (toCt, fromCt) = toFromContacts to from Contact {contactId = toContactId, localDisplayName = toLDN} = toCt Contact {contactId = fromContactId, localDisplayName = fromLDN} = fromCt @@ -1670,7 +1685,7 @@ mergeContactRecords db user@User {userId} to@Contact {localDisplayName = keepLDN WHERE user_id = ? AND local_display_name = ? |] (keepLDN, currentTs, userId, toLDN) - getContact db user toContactId + getContact db vr user toContactId where toFromContacts :: Contact -> Contact -> (Contact, Contact) toFromContacts c1 c2 @@ -1701,9 +1716,10 @@ associateMemberWithContactRecord when (memProfileId /= profileId) $ deleteUnusedProfile_ db userId memProfileId when (memLDN /= localDisplayName) $ deleteUnusedDisplayName_ db userId memLDN -associateContactWithMemberRecord :: DB.Connection -> User -> GroupMember -> Contact -> ExceptT StoreError IO Contact +associateContactWithMemberRecord :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupMember -> Contact -> ExceptT StoreError IO Contact associateContactWithMemberRecord db + vr user@User {userId} GroupMember {groupId, groupMemberId, localDisplayName = memLDN, memberProfile = LocalProfile {profileId = memProfileId}} Contact {contactId, localDisplayName, profile = LocalProfile {profileId}} = do @@ -1727,7 +1743,7 @@ associateContactWithMemberRecord (memLDN, memProfileId, currentTs, userId, contactId) when (profileId /= memProfileId) $ deleteUnusedProfile_ db userId profileId when (localDisplayName /= memLDN) $ deleteUnusedDisplayName_ db userId localDisplayName - getContact db user contactId + getContact db vr user contactId deleteUnusedDisplayName_ :: DB.Connection -> UserId -> ContactName -> IO () deleteUnusedDisplayName_ db userId localDisplayName = @@ -1875,7 +1891,7 @@ createMemberContact cReq gInfo GroupMember {groupMemberId, localDisplayName, memberProfile, memberContactProfileId} - Connection {connLevel, peerChatVRange = peerChatVRange@(JVersionRange (VersionRange minV maxV))} + Connection {connLevel, connChatVersion, peerChatVRange = peerChatVRange@(VersionRange minV maxV)} subMode = do currentTs <- getCurrentTime let incognitoProfile = incognitoMembershipProfile gInfo @@ -1902,25 +1918,49 @@ createMemberContact [sql| INSERT INTO connections ( user_id, agent_conn_id, conn_req_inv, conn_level, conn_status, conn_type, contact_conn_initiated, contact_id, custom_user_profile_id, - peer_chat_min_version, peer_chat_max_version, created_at, updated_at, to_subscribe - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?) + conn_chat_version, peer_chat_min_version, peer_chat_max_version, created_at, updated_at, to_subscribe + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] ( (userId, acId, cReq, connLevel, ConnNew, ConnContact, True, contactId, customUserProfileId) - :. (minV, maxV, currentTs, currentTs, subMode == SMOnlyCreate) + :. (connChatVersion, minV, maxV, currentTs, currentTs, subMode == SMOnlyCreate) ) connId <- insertedRowId db - let ctConn = Connection {connId, agentConnId = AgentConnId acId, peerChatVRange, connType = ConnContact, contactConnInitiated = True, entityId = Just contactId, viaContact = Nothing, viaUserContactLink = Nothing, viaGroupLink = False, groupLinkId = Nothing, customUserProfileId, connLevel, connStatus = ConnNew, localAlias = "", createdAt = currentTs, connectionCode = Nothing, authErrCounter = 0} + let ctConn = + Connection + { connId, + agentConnId = AgentConnId acId, + peerChatVRange, + connChatVersion, + connType = ConnContact, + contactConnInitiated = True, + entityId = Just contactId, + viaContact = Nothing, + viaUserContactLink = Nothing, + viaGroupLink = False, + groupLinkId = Nothing, + customUserProfileId, + connLevel, + connStatus = ConnNew, + localAlias = "", + createdAt = currentTs, + connectionCode = Nothing, + pqSupport = PQSupportOff, + pqEncryption = PQEncOff, + pqSndEnabled = Nothing, + pqRcvEnabled = Nothing, + authErrCounter = 0 + } mergedPreferences = contactUserPreferences user userPreferences preferences $ connIncognito ctConn pure Contact {contactId, localDisplayName, profile = memberProfile, activeConn = Just ctConn, viaGroup = Nothing, contactUsed = True, contactStatus = CSActive, chatSettings = defaultChatSettings, userPreferences, mergedPreferences, createdAt = currentTs, updatedAt = currentTs, chatTs = Just currentTs, contactGroupMemberId = Just groupMemberId, contactGrpInvSent = False} -getMemberContact :: DB.Connection -> VersionRange -> User -> ContactId -> ExceptT StoreError IO (GroupInfo, GroupMember, Contact, ConnReqInvitation) +getMemberContact :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ContactId -> ExceptT StoreError IO (GroupInfo, GroupMember, Contact, ConnReqInvitation) getMemberContact db vr user contactId = do - ct <- getContact db user contactId + ct <- getContact db vr user contactId let Contact {contactGroupMemberId, activeConn} = ct case (activeConn, contactGroupMemberId) of (Just Connection {connId}, Just groupMemberId) -> do cReq <- getConnReqInv db connId - m@GroupMember {groupId} <- getGroupMemberById db user groupMemberId + m@GroupMember {groupId} <- getGroupMemberById db vr user groupMemberId g <- getGroupInfo db vr user groupId pure (g, m, ct, cReq) _ -> @@ -2000,7 +2040,7 @@ createMemberContactConn_ user@User {userId} (cmdId, acId) gInfo - _memberConn@Connection {connLevel, peerChatVRange = peerChatVRange@(JVersionRange (VersionRange minV maxV))} + _memberConn@Connection {connLevel, connChatVersion, peerChatVRange = peerChatVRange@(VersionRange minV maxV)} contactId subMode = do currentTs <- liftIO getCurrentTime @@ -2010,15 +2050,39 @@ createMemberContactConn_ [sql| INSERT INTO connections ( user_id, agent_conn_id, conn_level, conn_status, conn_type, contact_id, custom_user_profile_id, - peer_chat_min_version, peer_chat_max_version, created_at, updated_at, to_subscribe - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?) + conn_chat_version, peer_chat_min_version, peer_chat_max_version, created_at, updated_at, to_subscribe + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?) |] ( (userId, acId, connLevel, ConnJoined, ConnContact, contactId, customUserProfileId) - :. (minV, maxV, currentTs, currentTs, subMode == SMOnlyCreate) + :. (connChatVersion, minV, maxV, currentTs, currentTs, subMode == SMOnlyCreate) ) connId <- insertedRowId db setCommandConnId db user cmdId connId - pure Connection {connId, agentConnId = AgentConnId acId, peerChatVRange, connType = ConnContact, contactConnInitiated = False, entityId = Just contactId, viaContact = Nothing, viaUserContactLink = Nothing, viaGroupLink = False, groupLinkId = Nothing, customUserProfileId, connLevel, connStatus = ConnJoined, localAlias = "", createdAt = currentTs, connectionCode = Nothing, authErrCounter = 0} + pure + Connection + { connId, + agentConnId = AgentConnId acId, + connChatVersion, + peerChatVRange, + connType = ConnContact, + contactConnInitiated = False, + entityId = Just contactId, + viaContact = Nothing, + viaUserContactLink = Nothing, + viaGroupLink = False, + groupLinkId = Nothing, + customUserProfileId, + connLevel, + connStatus = ConnJoined, + localAlias = "", + createdAt = currentTs, + connectionCode = Nothing, + pqSupport = PQSupportOff, + pqEncryption = PQEncOff, + pqSndEnabled = Nothing, + pqRcvEnabled = Nothing, + authErrCounter = 0 + } updateMemberProfile :: DB.Connection -> User -> GroupMember -> Profile -> ExceptT StoreError IO GroupMember updateMemberProfile db user@User {userId} m p' @@ -2069,7 +2133,7 @@ setXGrpLinkMemReceived db mId xGrpLinkMemReceived = do "UPDATE group_members SET xgrplinkmem_received = ?, updated_at = ? WHERE group_member_id = ?" (xGrpLinkMemReceived, currentTs, mId) -createNewUnknownGroupMember :: DB.Connection -> VersionRange -> User -> GroupInfo -> MemberId -> Text -> ExceptT StoreError IO GroupMember +createNewUnknownGroupMember :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupInfo -> MemberId -> Text -> ExceptT StoreError IO GroupMember createNewUnknownGroupMember db vr user@User {userId, userContactId} GroupInfo {groupId} memberId memberName = do currentTs <- liftIO getCurrentTime let memberProfile = profileFromName memberName @@ -2089,12 +2153,12 @@ createNewUnknownGroupMember db vr user@User {userId, userContactId} GroupInfo {g :. (minV, maxV) ) insertedRowId db - getGroupMemberById db user groupMemberId + getGroupMemberById db vr user groupMemberId where - VersionRange minV maxV = vr + VersionRange minV maxV = vr PQSupportOff -updateUnknownMemberAnnounced :: DB.Connection -> User -> GroupMember -> GroupMember -> MemberInfo -> ExceptT StoreError IO GroupMember -updateUnknownMemberAnnounced db user@User {userId} invitingMember unknownMember@GroupMember {groupMemberId, memberChatVRange} MemberInfo {memberRole, v, profile} = do +updateUnknownMemberAnnounced :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupMember -> GroupMember -> MemberInfo -> ExceptT StoreError IO GroupMember +updateUnknownMemberAnnounced db vr user@User {userId} invitingMember unknownMember@GroupMember {groupMemberId, memberChatVRange} MemberInfo {memberRole, v, profile} = do _ <- updateMemberProfile db user unknownMember profile currentTs <- liftIO getCurrentTime liftIO $ @@ -2114,9 +2178,9 @@ updateUnknownMemberAnnounced db user@User {userId} invitingMember unknownMember@ ( (memberRole, GCPostMember, GSMemAnnounced, groupMemberId' invitingMember) :. (minV, maxV, currentTs, userId, groupMemberId) ) - getGroupMemberById db user groupMemberId + getGroupMemberById db vr user groupMemberId where - VersionRange minV maxV = maybe (fromJVersionRange memberChatVRange) fromChatVRange v + VersionRange minV maxV = maybe memberChatVRange fromChatVRange v updateUserMemberProfileSentAt :: DB.Connection -> User -> GroupInfo -> UTCTime -> IO () updateUserMemberProfileSentAt db User {userId} GroupInfo {groupId} sentTs = diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index 891e9887e6..bf74add6db 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -39,7 +39,7 @@ module Simplex.Chat.Store.Messages getDirectChat, getGroupChat, getLocalChat, - getDirectChatItemsLast, + getDirectChatItemLast, getAllChatItems, getAChatItem, updateDirectChatItem, @@ -92,6 +92,7 @@ module Simplex.Chat.Store.Messages getLocalChatItemIdByText, getLocalChatItemIdByText', getChatItemByFileId, + lookupChatItemByFileId, getChatItemByGroupId, updateDirectChatItemStatus, getTimedItems, @@ -125,6 +126,7 @@ import Data.List (sortBy) import Data.Maybe (fromMaybe, isJust, mapMaybe) import Data.Ord (Down (..), comparing) import Data.Text (Text) +import qualified Data.Text as T import Data.Time (addUTCTime) import Data.Time.Clock (UTCTime (..), getCurrentTime) import Database.SQLite.Simple (NamedParam (..), Only (..), Query, (:.) (..)) @@ -143,9 +145,9 @@ import Simplex.Messaging.Agent.Protocol (AgentMsgId, ConnId, MsgMeta (..), UserI import Simplex.Messaging.Agent.Store.SQLite (firstRow, firstRow', maybeFirstRow) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Crypto as C +import Simplex.Messaging.Crypto.Ratchet (PQSupport) import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import Simplex.Messaging.Util (eitherToMaybe) -import Simplex.Messaging.Version (VersionRange) import UnliftIO.STM deleteContactCIs :: DB.Connection -> User -> Contact -> IO () @@ -469,7 +471,8 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe FROM group_members m JOIN contact_profiles p ON p.contact_profile_id = COALESCE(m.member_profile_id, m.contact_profile_id) LEFT JOIN contacts c ON m.contact_id = c.contact_id - LEFT JOIN chat_items i ON i.group_id = m.group_id + LEFT JOIN chat_items i ON i.user_id = m.user_id + AND i.group_id = m.group_id AND m.group_member_id = i.group_member_id AND i.shared_msg_id = :msg_id WHERE m.user_id = :user_id AND m.group_id = :group_id AND m.member_id = :member_id @@ -480,7 +483,7 @@ getChatItemQuote_ db User {userId, userContactId} chatDirection QuotedMsg {msgRe ciQuoteGroup [] = ciQuote Nothing $ CIQGroupRcv Nothing ciQuoteGroup ((Only itemId :. memberRow) : _) = ciQuote itemId . CIQGroupRcv . Just $ toGroupMember userContactId memberRow -getChatPreviews :: DB.Connection -> VersionRange -> User -> Bool -> PaginationByTime -> ChatListQuery -> IO [Either StoreError AChat] +getChatPreviews :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Bool -> PaginationByTime -> ChatListQuery -> IO [Either StoreError AChat] getChatPreviews db vr user withPCC pagination query = do directChats <- findDirectChatPreviews_ db user pagination query groupChats <- findGroupChatPreviews_ db user pagination query @@ -503,7 +506,7 @@ getChatPreviews db vr user withPCC pagination query = do PTBefore _ count -> take count . sortBy (comparing $ Down . ts) getChatPreview :: AChatPreviewData -> ExceptT StoreError IO AChat getChatPreview (ACPD cType cpd) = case cType of - SCTDirect -> getDirectChatPreview_ db user cpd + SCTDirect -> getDirectChatPreview_ db vr user cpd SCTGroup -> getGroupChatPreview_ db vr user cpd SCTLocal -> getLocalChatPreview_ db user cpd SCTContactRequest -> let (ContactRequestPD _ chat) = cpd in pure chat @@ -617,9 +620,9 @@ findDirectChatPreviews_ db User {userId} pagination clq = ) ([":user_id" := userId, ":rcv_new" := CISRcvNew, ":search" := search] <> pagParams) -getDirectChatPreview_ :: DB.Connection -> User -> ChatPreviewData 'CTDirect -> ExceptT StoreError IO AChat -getDirectChatPreview_ db user (DirectChatPD _ contactId lastItemId_ stats) = do - contact <- getContact db user contactId +getDirectChatPreview_ :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ChatPreviewData 'CTDirect -> ExceptT StoreError IO AChat +getDirectChatPreview_ db vr user (DirectChatPD _ contactId lastItemId_ stats) = do + contact <- getContact db vr user contactId lastItem <- case lastItemId_ of Just lastItemId -> (: []) <$> getDirectChatItem db user contactId lastItemId Nothing -> pure [] @@ -713,7 +716,7 @@ findGroupChatPreviews_ db User {userId} pagination clq = ) ([":user_id" := userId, ":rcv_new" := CISRcvNew, ":search" := search] <> pagParams) -getGroupChatPreview_ :: DB.Connection -> VersionRange -> User -> ChatPreviewData 'CTGroup -> ExceptT StoreError IO AChat +getGroupChatPreview_ :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ChatPreviewData 'CTGroup -> ExceptT StoreError IO AChat getGroupChatPreview_ db vr user (GroupChatPD _ groupId lastItemId_ stats) = do groupInfo <- getGroupInfo db vr user groupId lastItem <- case lastItemId_ of @@ -828,7 +831,7 @@ toLocalChatItem currentTs ((itemId, itemTs, AMsgDirection msgDir, itemContentTex cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection 'CTLocal d -> CIStatus d -> CIContent d -> Maybe (CIFile d) -> CChatItem 'CTLocal cItem d chatDir ciStatus content file = CChatItem d ChatItem {chatDir, meta = ciMeta content ciStatus, content, formattedText = parseMaybeMarkdownList itemText, quotedItem = Nothing, reactions = [], file} - badItem = Left $ SEBadChatItem itemId + badItem = Left $ SEBadChatItem itemId (Just itemTs) ciMeta :: CIContent d -> CIStatus d -> CIMeta 'CTLocal d ciMeta content status = let itemDeleted' = case itemDeleted of @@ -855,7 +858,7 @@ getContactRequestChatPreviews_ db User {userId} pagination clq = case clq of ( [sql| SELECT cr.contact_request_id, cr.local_display_name, cr.agent_invitation_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, p.preferences, + 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 FROM contact_requests cr @@ -918,119 +921,141 @@ getContactConnectionChatPreviews_ db User {userId} pagination clq = case clq of aChat = AChat SCTContactConnection $ Chat (ContactConnection conn) [] stats in ACPD SCTContactConnection $ ContactConnectionPD updatedAt aChat -getDirectChat :: DB.Connection -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTDirect) -getDirectChat db user contactId pagination search_ = do +getDirectChat :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTDirect) +getDirectChat db vr user contactId pagination search_ = do let search = fromMaybe "" search_ - ct <- getContact db user contactId - liftIO . getDirectChatReactions_ db ct =<< case pagination of + ct <- getContact db vr user contactId + liftIO $ case pagination of CPLast count -> getDirectChatLast_ db user ct count search CPAfter afterId count -> getDirectChatAfter_ db user ct afterId count search CPBefore beforeId count -> getDirectChatBefore_ db user ct beforeId count search -getDirectChatLast_ :: DB.Connection -> User -> Contact -> Int -> String -> ExceptT StoreError IO (Chat 'CTDirect) -getDirectChatLast_ db user ct@Contact {contactId} count search = do - let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - chatItems <- getDirectChatItemsLast db user contactId count search - pure $ Chat (DirectChat ct) (reverse chatItems) stats - -- the last items in reverse order (the last item in the conversation is the first in the returned list) -getDirectChatItemsLast :: DB.Connection -> User -> ContactId -> Int -> String -> ExceptT StoreError IO [CChatItem 'CTDirect] -getDirectChatItemsLast db User {userId} contactId count search = ExceptT $ do - currentTs <- getCurrentTime - mapM (toDirectChatItem currentTs) - <$> DB.query - db - [sql| - SELECT - -- ChatItem - i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, - -- CIFile - f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol, - -- DirectQuote - ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent - FROM chat_items i - LEFT JOIN files f ON f.chat_item_id = i.chat_item_id - LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id - WHERE i.user_id = ? AND i.contact_id = ? AND i.item_text LIKE '%' || ? || '%' - ORDER BY i.created_at DESC, i.chat_item_id DESC - LIMIT ? - |] - (userId, contactId, search, count) - -getDirectChatAfter_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTDirect) -getDirectChatAfter_ db User {userId} ct@Contact {contactId} afterChatItemId count search = do +getDirectChatLast_ :: DB.Connection -> User -> Contact -> Int -> String -> IO (Chat 'CTDirect) +getDirectChatLast_ db user@User {userId} ct@Contact {contactId} count search = do let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - chatItems <- ExceptT getDirectChatItemsAfter_ - pure $ Chat (DirectChat ct) chatItems stats + chatItemIds <- getDirectChatItemIdsLast_ + currentTs <- getCurrentTime + chatItems <- mapM (safeGetDirectItem db user ct currentTs) chatItemIds + pure $ Chat (DirectChat ct) (reverse chatItems) stats where - getDirectChatItemsAfter_ :: IO (Either StoreError [CChatItem 'CTDirect]) - getDirectChatItemsAfter_ = do - currentTs <- getCurrentTime - mapM (toDirectChatItem currentTs) + getDirectChatItemIdsLast_ :: IO [ChatItemId] + getDirectChatItemIdsLast_ = + map fromOnly <$> DB.query db [sql| - SELECT - -- ChatItem - i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, - -- CIFile - f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol, - -- DirectQuote - ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent - FROM chat_items i - LEFT JOIN files f ON f.chat_item_id = i.chat_item_id - LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id - WHERE i.user_id = ? AND i.contact_id = ? AND i.item_text LIKE '%' || ? || '%' - AND i.chat_item_id > ? - ORDER BY i.created_at ASC, i.chat_item_id ASC + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND contact_id = ? AND item_text LIKE '%' || ? || '%' + ORDER BY created_at DESC, chat_item_id DESC + LIMIT ? + |] + (userId, contactId, search, count) + +safeGetDirectItem :: DB.Connection -> User -> Contact -> UTCTime -> ChatItemId -> IO (CChatItem 'CTDirect) +safeGetDirectItem db user ct currentTs itemId = + runExceptT (getDirectCIWithReactions db user ct itemId) + >>= pure <$> safeToDirectItem currentTs itemId + +safeToDirectItem :: UTCTime -> ChatItemId -> Either StoreError (CChatItem 'CTDirect) -> CChatItem 'CTDirect +safeToDirectItem currentTs itemId = \case + Right ci -> ci + Left e@(SEBadChatItem _ (Just itemTs)) -> badDirectItem itemTs e + Left e -> badDirectItem currentTs e + where + badDirectItem :: UTCTime -> StoreError -> CChatItem 'CTDirect + badDirectItem ts e = + let errorText = T.pack $ show e + in CChatItem + SMDSnd + ChatItem + { chatDir = CIDirectSnd, + meta = dummyMeta itemId ts errorText, + content = CIInvalidJSON errorText, + formattedText = Nothing, + quotedItem = Nothing, + reactions = [], + file = Nothing + } + +getDirectChatItemLast :: DB.Connection -> User -> ContactId -> ExceptT StoreError IO (CChatItem 'CTDirect) +getDirectChatItemLast db user@User {userId} contactId = do + chatItemId <- + ExceptT . firstRow fromOnly (SEChatItemNotFoundByContactId contactId) $ + DB.query + db + [sql| + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND contact_id = ? + ORDER BY created_at DESC, chat_item_id DESC + LIMIT 1 + |] + (userId, contactId) + getDirectChatItem db user contactId chatItemId + +getDirectChatAfter_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> IO (Chat 'CTDirect) +getDirectChatAfter_ db user@User {userId} ct@Contact {contactId} afterChatItemId count search = do + let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} + chatItemIds <- getDirectChatItemIdsAfter_ + currentTs <- getCurrentTime + chatItems <- mapM (safeGetDirectItem db user ct currentTs) chatItemIds + pure $ Chat (DirectChat ct) chatItems stats + where + getDirectChatItemIdsAfter_ :: IO [ChatItemId] + getDirectChatItemIdsAfter_ = + map fromOnly + <$> DB.query + db + [sql| + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND contact_id = ? AND item_text LIKE '%' || ? || '%' + AND chat_item_id > ? + ORDER BY created_at ASC, chat_item_id ASC LIMIT ? |] (userId, contactId, search, afterChatItemId, count) -getDirectChatBefore_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTDirect) -getDirectChatBefore_ db User {userId} ct@Contact {contactId} beforeChatItemId count search = do +getDirectChatBefore_ :: DB.Connection -> User -> Contact -> ChatItemId -> Int -> String -> IO (Chat 'CTDirect) +getDirectChatBefore_ db user@User {userId} ct@Contact {contactId} beforeChatItemId count search = do let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - chatItems <- ExceptT getDirectChatItemsBefore_ + chatItemIds <- getDirectChatItemsIdsBefore_ + currentTs <- getCurrentTime + chatItems <- mapM (safeGetDirectItem db user ct currentTs) chatItemIds pure $ Chat (DirectChat ct) (reverse chatItems) stats where - getDirectChatItemsBefore_ :: IO (Either StoreError [CChatItem 'CTDirect]) - getDirectChatItemsBefore_ = do - currentTs <- getCurrentTime - mapM (toDirectChatItem currentTs) + getDirectChatItemsIdsBefore_ :: IO [ChatItemId] + getDirectChatItemsIdsBefore_ = + map fromOnly <$> DB.query db [sql| - SELECT - -- ChatItem - i.chat_item_id, i.item_ts, i.item_sent, i.item_content, i.item_text, i.item_status, i.shared_msg_id, i.item_deleted, i.item_deleted_ts, i.item_edited, i.created_at, i.updated_at, i.timed_ttl, i.timed_delete_at, i.item_live, - -- CIFile - f.file_id, f.file_name, f.file_size, f.file_path, f.file_crypto_key, f.file_crypto_nonce, f.ci_file_status, f.protocol, - -- DirectQuote - ri.chat_item_id, i.quoted_shared_msg_id, i.quoted_sent_at, i.quoted_content, i.quoted_sent - FROM chat_items i - LEFT JOIN files f ON f.chat_item_id = i.chat_item_id - LEFT JOIN chat_items ri ON ri.user_id = i.user_id AND ri.contact_id = i.contact_id AND ri.shared_msg_id = i.quoted_shared_msg_id - WHERE i.user_id = ? AND i.contact_id = ? AND i.item_text LIKE '%' || ? || '%' - AND i.chat_item_id < ? - ORDER BY i.created_at DESC, i.chat_item_id DESC + SELECT chat_item_id + FROM chat_items + WHERE user_id = ? AND contact_id = ? AND item_text LIKE '%' || ? || '%' + AND chat_item_id < ? + ORDER BY created_at DESC, chat_item_id DESC LIMIT ? |] (userId, contactId, search, beforeChatItemId, count) -getGroupChat :: DB.Connection -> VersionRange -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTGroup) +getGroupChat :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Int64 -> ChatPagination -> Maybe String -> ExceptT StoreError IO (Chat 'CTGroup) getGroupChat db vr user groupId pagination search_ = do let search = fromMaybe "" search_ g <- getGroupInfo db vr user groupId case pagination of - CPLast count -> getGroupChatLast_ db user g count search + CPLast count -> liftIO $ getGroupChatLast_ db user g count search CPAfter afterId count -> getGroupChatAfter_ db user g afterId count search CPBefore beforeId count -> getGroupChatBefore_ db user g beforeId count search -getGroupChatLast_ :: DB.Connection -> User -> GroupInfo -> Int -> String -> ExceptT StoreError IO (Chat 'CTGroup) +getGroupChatLast_ :: DB.Connection -> User -> GroupInfo -> Int -> String -> IO (Chat 'CTGroup) getGroupChatLast_ db user@User {userId} g@GroupInfo {groupId} count search = do let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - chatItemIds <- liftIO getGroupChatItemIdsLast_ - chatItems <- mapM (getGroupCIWithReactions db user g) chatItemIds + chatItemIds <- getGroupChatItemIdsLast_ + currentTs <- getCurrentTime + chatItems <- mapM (safeGetGroupItem db user g currentTs) chatItemIds pure $ Chat (GroupChat g) (reverse chatItems) stats where getGroupChatItemIdsLast_ :: IO [ChatItemId] @@ -1047,6 +1072,32 @@ getGroupChatLast_ db user@User {userId} g@GroupInfo {groupId} count search = do |] (userId, groupId, search, count) +safeGetGroupItem :: DB.Connection -> User -> GroupInfo -> UTCTime -> ChatItemId -> IO (CChatItem 'CTGroup) +safeGetGroupItem db user g currentTs itemId = + runExceptT (getGroupCIWithReactions db user g itemId) + >>= pure <$> safeToGroupItem currentTs itemId + +safeToGroupItem :: UTCTime -> ChatItemId -> Either StoreError (CChatItem 'CTGroup) -> CChatItem 'CTGroup +safeToGroupItem currentTs itemId = \case + Right ci -> ci + Left e@(SEBadChatItem _ (Just itemTs)) -> badGroupItem itemTs e + Left e -> badGroupItem currentTs e + where + badGroupItem :: UTCTime -> StoreError -> CChatItem 'CTGroup + badGroupItem ts e = + let errorText = T.pack $ show e + in CChatItem + SMDSnd + ChatItem + { chatDir = CIGroupSnd, + meta = dummyMeta itemId ts errorText, + content = CIInvalidJSON errorText, + formattedText = Nothing, + quotedItem = Nothing, + reactions = [], + file = Nothing + } + getGroupMemberChatItemLast :: DB.Connection -> User -> GroupId -> GroupMemberId -> ExceptT StoreError IO (CChatItem 'CTGroup) getGroupMemberChatItemLast db user@User {userId} groupId groupMemberId = do chatItemId <- @@ -1068,7 +1119,8 @@ getGroupChatAfter_ db user@User {userId} g@GroupInfo {groupId} afterChatItemId c let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} afterChatItem <- getGroupChatItem db user groupId afterChatItemId chatItemIds <- liftIO $ getGroupChatItemIdsAfter_ (chatItemTs afterChatItem) - chatItems <- mapM (getGroupCIWithReactions db user g) chatItemIds + currentTs <- liftIO getCurrentTime + chatItems <- liftIO $ mapM (safeGetGroupItem db user g currentTs) chatItemIds pure $ Chat (GroupChat g) chatItems stats where getGroupChatItemIdsAfter_ :: UTCTime -> IO [ChatItemId] @@ -1091,7 +1143,8 @@ getGroupChatBefore_ db user@User {userId} g@GroupInfo {groupId} beforeChatItemId let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} beforeChatItem <- getGroupChatItem db user groupId beforeChatItemId chatItemIds <- liftIO $ getGroupChatItemIdsBefore_ (chatItemTs beforeChatItem) - chatItems <- mapM (getGroupCIWithReactions db user g) chatItemIds + currentTs <- liftIO getCurrentTime + chatItems <- liftIO $ mapM (safeGetGroupItem db user g currentTs) chatItemIds pure $ Chat (GroupChat g) (reverse chatItems) stats where getGroupChatItemIdsBefore_ :: UTCTime -> IO [ChatItemId] @@ -1113,16 +1166,17 @@ getLocalChat :: DB.Connection -> User -> Int64 -> ChatPagination -> Maybe String getLocalChat db user folderId pagination search_ = do let search = fromMaybe "" search_ nf <- getNoteFolder db user folderId - case pagination of + liftIO $ case pagination of CPLast count -> getLocalChatLast_ db user nf count search CPAfter afterId count -> getLocalChatAfter_ db user nf afterId count search CPBefore beforeId count -> getLocalChatBefore_ db user nf beforeId count search -getLocalChatLast_ :: DB.Connection -> User -> NoteFolder -> Int -> String -> ExceptT StoreError IO (Chat 'CTLocal) +getLocalChatLast_ :: DB.Connection -> User -> NoteFolder -> Int -> String -> IO (Chat 'CTLocal) getLocalChatLast_ db user@User {userId} nf@NoteFolder {noteFolderId} count search = do let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - chatItemIds <- liftIO getLocalChatItemIdsLast_ - chatItems <- mapM (getLocalChatItem db user noteFolderId) chatItemIds + chatItemIds <- getLocalChatItemIdsLast_ + currentTs <- getCurrentTime + chatItems <- mapM (safeGetLocalItem db user nf currentTs) chatItemIds pure $ Chat (LocalChat nf) (reverse chatItems) stats where getLocalChatItemIdsLast_ :: IO [ChatItemId] @@ -1139,11 +1193,38 @@ getLocalChatLast_ db user@User {userId} nf@NoteFolder {noteFolderId} count searc |] (userId, noteFolderId, search, count) -getLocalChatAfter_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTLocal) +safeGetLocalItem :: DB.Connection -> User -> NoteFolder -> UTCTime -> ChatItemId -> IO (CChatItem 'CTLocal) +safeGetLocalItem db user NoteFolder {noteFolderId} currentTs itemId = + runExceptT (getLocalChatItem db user noteFolderId itemId) + >>= pure <$> safeToLocalItem currentTs itemId + +safeToLocalItem :: UTCTime -> ChatItemId -> Either StoreError (CChatItem 'CTLocal) -> CChatItem 'CTLocal +safeToLocalItem currentTs itemId = \case + Right ci -> ci + Left e@(SEBadChatItem _ (Just itemTs)) -> badLocalItem itemTs e + Left e -> badLocalItem currentTs e + where + badLocalItem :: UTCTime -> StoreError -> CChatItem 'CTLocal + badLocalItem ts e = + let errorText = T.pack $ show e + in CChatItem + SMDSnd + ChatItem + { chatDir = CILocalSnd, + meta = dummyMeta itemId ts errorText, + content = CIInvalidJSON errorText, + formattedText = Nothing, + quotedItem = Nothing, + reactions = [], + file = Nothing + } + +getLocalChatAfter_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> IO (Chat 'CTLocal) getLocalChatAfter_ db user@User {userId} nf@NoteFolder {noteFolderId} afterChatItemId count search = do let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - chatItemIds <- liftIO getLocalChatItemIdsAfter_ - chatItems <- mapM (getLocalChatItem db user noteFolderId) chatItemIds + chatItemIds <- getLocalChatItemIdsAfter_ + currentTs <- getCurrentTime + chatItems <- mapM (safeGetLocalItem db user nf currentTs) chatItemIds pure $ Chat (LocalChat nf) chatItems stats where getLocalChatItemIdsAfter_ :: IO [ChatItemId] @@ -1161,11 +1242,12 @@ getLocalChatAfter_ db user@User {userId} nf@NoteFolder {noteFolderId} afterChatI |] (userId, noteFolderId, search, afterChatItemId, count) -getLocalChatBefore_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> ExceptT StoreError IO (Chat 'CTLocal) +getLocalChatBefore_ :: DB.Connection -> User -> NoteFolder -> ChatItemId -> Int -> String -> IO (Chat 'CTLocal) getLocalChatBefore_ db user@User {userId} nf@NoteFolder {noteFolderId} beforeChatItemId count search = do let stats = ChatStats {unreadCount = 0, minUnreadItemId = 0, unreadChat = False} - chatItemIds <- liftIO getLocalChatItemIdsBefore_ - chatItems <- mapM (getLocalChatItem db user noteFolderId) chatItemIds + chatItemIds <- getLocalChatItemIdsBefore_ + currentTs <- getCurrentTime + chatItems <- mapM (safeGetLocalItem db user nf currentTs) chatItemIds pure $ Chat (LocalChat nf) (reverse chatItems) stats where getLocalChatItemIdsBefore_ :: IO [ChatItemId] @@ -1188,7 +1270,7 @@ toChatItemRef = \case (itemId, Just contactId, Nothing, Nothing) -> Right (ChatRef CTDirect contactId, itemId) (itemId, Nothing, Just groupId, Nothing) -> Right (ChatRef CTGroup groupId, itemId) (itemId, Nothing, Nothing, Just folderId) -> Right (ChatRef CTLocal folderId, itemId) - (itemId, _, _, _) -> Left $ SEBadChatItem itemId + (itemId, _, _, _) -> Left $ SEBadChatItem itemId Nothing updateDirectChatItemsRead :: DB.Connection -> User -> ContactId -> Maybe (ChatItemId, ChatItemId) -> IO () updateDirectChatItemsRead db User {userId} contactId itemsRange_ = do @@ -1361,7 +1443,7 @@ toDirectChatItem currentTs (((itemId, itemTs, AMsgDirection msgDir, itemContentT cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection 'CTDirect d -> CIStatus d -> CIContent d -> Maybe (CIFile d) -> CChatItem 'CTDirect cItem d chatDir ciStatus content file = CChatItem d ChatItem {chatDir, meta = ciMeta content ciStatus, content, formattedText = parseMaybeMarkdownList itemText, quotedItem = toDirectQuote quoteRow, reactions = [], file} - badItem = Left $ SEBadChatItem itemId + badItem = Left $ SEBadChatItem itemId (Just itemTs) ciMeta :: CIContent d -> CIStatus d -> CIMeta 'CTDirect d ciMeta content status = let itemDeleted' = case itemDeleted of @@ -1412,7 +1494,7 @@ toGroupChatItem currentTs userContactId (((itemId, itemTs, AMsgDirection msgDir, cItem :: MsgDirectionI d => SMsgDirection d -> CIDirection 'CTGroup d -> CIStatus d -> CIContent d -> Maybe (CIFile d) -> CChatItem 'CTGroup cItem d chatDir ciStatus content file = CChatItem d ChatItem {chatDir, meta = ciMeta content ciStatus, content, formattedText = parseMaybeMarkdownList itemText, quotedItem = toGroupQuote quoteRow quotedMember_, reactions = [], file} - badItem = Left $ SEBadChatItem itemId + badItem = Left $ SEBadChatItem itemId (Just itemTs) ciMeta :: CIContent d -> CIStatus d -> CIMeta 'CTGroup d ciMeta content status = let itemDeleted' = case itemDeleted of @@ -1425,7 +1507,7 @@ toGroupChatItem currentTs userContactId (((itemId, itemTs, AMsgDirection msgDir, ciTimed :: Maybe CITimed ciTimed = timedTTL >>= \ttl -> Just CITimed {ttl, deleteAt = timedDeleteAt} -getAllChatItems :: DB.Connection -> VersionRange -> User -> ChatPagination -> Maybe String -> ExceptT StoreError IO [AChatItem] +getAllChatItems :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ChatPagination -> Maybe String -> ExceptT StoreError IO [AChatItem] getAllChatItems db vr user@User {userId} pagination search_ = do itemRefs <- rights . map toChatItemRef <$> case pagination of @@ -2069,7 +2151,7 @@ deleteLocalChatItem db User {userId} NoteFolder {noteFolderId} ci = do |] (userId, noteFolderId, itemId) -getChatItemByFileId :: DB.Connection -> VersionRange -> User -> Int64 -> ExceptT StoreError IO AChatItem +getChatItemByFileId :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Int64 -> ExceptT StoreError IO AChatItem getChatItemByFileId db vr user@User {userId} fileId = do (chatRef, itemId) <- ExceptT . firstRow' toChatItemRef (SEChatItemNotFoundByFileId fileId) $ @@ -2085,7 +2167,13 @@ getChatItemByFileId db vr user@User {userId} fileId = do (userId, fileId) getAChatItem db vr user chatRef itemId -getChatItemByGroupId :: DB.Connection -> VersionRange -> User -> GroupId -> ExceptT StoreError IO AChatItem +lookupChatItemByFileId :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> Int64 -> ExceptT StoreError IO (Maybe AChatItem) +lookupChatItemByFileId db vr user fileId = do + fmap Just (getChatItemByFileId db vr user fileId) `catchError` \case + SEChatItemNotFoundByFileId {} -> pure Nothing + e -> throwError e + +getChatItemByGroupId :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupId -> ExceptT StoreError IO AChatItem getChatItemByGroupId db vr user@User {userId} groupId = do (chatRef, itemId) <- ExceptT . firstRow' toChatItemRef (SEChatItemNotFoundByGroupId groupId) $ @@ -2109,12 +2197,12 @@ getChatRefViaItemId db User {userId} itemId = do toChatRef = \case (Just contactId, Nothing) -> Right $ ChatRef CTDirect contactId (Nothing, Just groupId) -> Right $ ChatRef CTGroup groupId - (_, _) -> Left $ SEBadChatItem itemId + (_, _) -> Left $ SEBadChatItem itemId Nothing -getAChatItem :: DB.Connection -> VersionRange -> User -> ChatRef -> ChatItemId -> ExceptT StoreError IO AChatItem +getAChatItem :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ChatRef -> ChatItemId -> ExceptT StoreError IO AChatItem getAChatItem db vr user chatRef itemId = case chatRef of ChatRef CTDirect contactId -> do - ct <- getContact db user contactId + ct <- getContact db vr user contactId (CChatItem msgDir ci) <- getDirectChatItem db user contactId itemId pure $ AChatItem SCTDirect msgDir (DirectChat ct) ci ChatRef CTGroup groupId -> do @@ -2145,11 +2233,6 @@ getChatItemVersions db itemId = do let formattedText = parseMaybeMarkdownList $ msgContentText msgContent in ChatItemVersion {chatItemVersionId, msgContent, formattedText, itemVersionTs, createdAt} -getDirectChatReactions_ :: DB.Connection -> Contact -> Chat 'CTDirect -> IO (Chat 'CTDirect) -getDirectChatReactions_ db ct c@Chat {chatItems} = do - chatItems' <- mapM (directCIWithReactions db ct) chatItems - pure c {chatItems = chatItems'} - directCIWithReactions :: DB.Connection -> Contact -> CChatItem 'CTDirect -> IO (CChatItem 'CTDirect) directCIWithReactions db ct cci@(CChatItem md ci@ChatItem {meta = CIMeta {itemSharedMsgId}}) = case itemSharedMsgId of Just sharedMsgId -> do @@ -2356,9 +2439,9 @@ createCIModeration db GroupInfo {groupId} moderatorMember itemMemberId itemShare |] (groupId, groupMemberId' moderatorMember, itemMemberId, itemSharedMId, msgId, moderatedAtTs) -getCIModeration :: DB.Connection -> User -> GroupInfo -> MemberId -> Maybe SharedMsgId -> IO (Maybe CIModeration) -getCIModeration _ _ _ _ Nothing = pure Nothing -getCIModeration db user GroupInfo {groupId} itemMemberId (Just sharedMsgId) = do +getCIModeration :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> GroupInfo -> MemberId -> Maybe SharedMsgId -> IO (Maybe CIModeration) +getCIModeration _ _ _ _ _ Nothing = pure Nothing +getCIModeration db vr user GroupInfo {groupId} itemMemberId (Just sharedMsgId) = do r_ <- maybeFirstRow id $ DB.query @@ -2372,7 +2455,7 @@ getCIModeration db user GroupInfo {groupId} itemMemberId (Just sharedMsgId) = do (groupId, itemMemberId, sharedMsgId) case r_ of Just (moderationId, moderatorId, createdByMsgId, moderatedAt) -> do - runExceptT (getGroupMember db user groupId moderatorId) >>= \case + runExceptT (getGroupMember db vr user groupId moderatorId) >>= \case Right moderatorMember -> pure (Just CIModeration {moderationId, moderatorMember, createdByMsgId, moderatedAt}) _ -> pure Nothing _ -> pure Nothing diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index 2f5e61a5e6..d8bdbd6fd3 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -98,6 +98,10 @@ import Simplex.Chat.Migrations.M20240102_note_folders import Simplex.Chat.Migrations.M20240104_members_profile_update import Simplex.Chat.Migrations.M20240115_block_member_for_all import Simplex.Chat.Migrations.M20240122_indexes +import Simplex.Chat.Migrations.M20240214_redirect_file_id +import Simplex.Chat.Migrations.M20240222_app_settings +import Simplex.Chat.Migrations.M20240226_users_restrict +import Simplex.Chat.Migrations.M20240228_pq import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -195,7 +199,11 @@ schemaMigrations = ("20240102_note_folders", m20240102_note_folders, Just down_m20240102_note_folders), ("20240104_members_profile_update", m20240104_members_profile_update, Just down_m20240104_members_profile_update), ("20240115_block_member_for_all", m20240115_block_member_for_all, Just down_m20240115_block_member_for_all), - ("20240122_indexes", m20240122_indexes, Just down_m20240122_indexes) + ("20240122_indexes", m20240122_indexes, Just down_m20240122_indexes), + ("20240214_redirect_file_id", m20240214_redirect_file_id, Just down_m20240214_redirect_file_id), + ("20240222_app_settings", m20240222_app_settings, Just down_m20240222_app_settings), + ("20240226_users_restrict", m20240226_users_restrict, Just down_m20240226_users_restrict), + ("20240228_pq", m20240228_pq, Just down_m20240228_pq) ] -- | The list of migrations in ascending order by date diff --git a/src/Simplex/Chat/Store/Profiles.hs b/src/Simplex/Chat/Store/Profiles.hs index eceb19ba34..512c857b23 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -85,6 +85,8 @@ import Simplex.Messaging.Agent.Protocol (ACorrId, ConnId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Crypto as C +import qualified Simplex.Messaging.Crypto.Ratchet as CR +import Simplex.Messaging.Crypto.Ratchet (PQSupport) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON) import Simplex.Messaging.Protocol (BasicAuth (..), ProtoServerWithAuth (..), ProtocolServer (..), ProtocolTypeI (..), SubscriptionMode) @@ -323,37 +325,39 @@ createUserContactLink db User {userId} agentConnId cReq subMode = "INSERT INTO user_contact_links (user_id, conn_req_contact, created_at, updated_at) VALUES (?,?,?,?)" (userId, cReq, currentTs, currentTs) userContactLinkId <- insertedRowId db - void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode + void $ createConnection_ db userId ConnUserContact (Just userContactLinkId) agentConnId initialChatVersion chatInitialVRange Nothing Nothing Nothing 0 currentTs subMode CR.PQSupportOff -getUserAddressConnections :: DB.Connection -> User -> ExceptT StoreError IO [Connection] -getUserAddressConnections db User {userId} = do +getUserAddressConnections :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> ExceptT StoreError IO [Connection] +getUserAddressConnections db vr User {userId} = do cs <- liftIO getUserAddressConnections_ if null cs then throwError SEUserContactLinkNotFound else pure cs where getUserAddressConnections_ :: IO [Connection] getUserAddressConnections_ = - map toConnection + map (toConnection vr) <$> DB.query db [sql| SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter, - c.peer_chat_min_version, c.peer_chat_max_version + c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, + c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version FROM connections c JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id WHERE c.user_id = ? AND uc.user_id = ? AND uc.local_display_name = '' AND uc.group_id IS NULL |] (userId, userId) -getUserContactLinks :: DB.Connection -> User -> IO [(Connection, UserContact)] -getUserContactLinks db User {userId} = +getUserContactLinks :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> IO [(Connection, UserContact)] +getUserContactLinks db vr User {userId} = map toUserContactConnection <$> DB.query db [sql| SELECT c.connection_id, c.agent_conn_id, c.conn_level, c.via_contact, c.via_user_contact_link, c.via_group_link, c.group_link_id, c.custom_user_profile_id, - c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, c.created_at, c.security_code, c.security_code_verified_at, c.auth_err_counter, - c.peer_chat_min_version, c.peer_chat_max_version, + c.conn_status, c.conn_type, c.contact_conn_initiated, c.local_alias, c.contact_id, c.group_member_id, c.snd_file_id, c.rcv_file_id, c.user_contact_link_id, + c.created_at, c.security_code, c.security_code_verified_at, c.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + c.conn_chat_version, c.peer_chat_min_version, c.peer_chat_max_version, uc.user_contact_link_id, uc.conn_req_contact, uc.group_id FROM connections c JOIN user_contact_links uc ON c.user_contact_link_id = uc.user_contact_link_id @@ -362,7 +366,7 @@ getUserContactLinks db User {userId} = (userId, userId) where toUserContactConnection :: (ConnectionRow :. (Int64, ConnReqContact, Maybe GroupId)) -> (Connection, UserContact) - toUserContactConnection (connRow :. (userContactLinkId, connReqContact, groupId)) = (toConnection connRow, UserContact {userContactLinkId, connReqContact, groupId}) + toUserContactConnection (connRow :. (userContactLinkId, connReqContact, groupId)) = (toConnection vr connRow, UserContact {userContactLinkId, connReqContact, groupId}) deleteUserAddress :: DB.Connection -> User -> IO () deleteUserAddress db user@User {userId} = do @@ -470,8 +474,8 @@ getUserContactLinkByConnReq db User {userId} (cReqSchema1, cReqSchema2) = |] (userId, cReqSchema1, cReqSchema2) -getContactWithoutConnViaAddress :: DB.Connection -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe Contact) -getContactWithoutConnViaAddress db user@User {userId} (cReqSchema1, cReqSchema2) = do +getContactWithoutConnViaAddress :: DB.Connection -> (PQSupport -> VersionRangeChat) -> User -> (ConnReqContact, ConnReqContact) -> IO (Maybe Contact) +getContactWithoutConnViaAddress db vr user@User {userId} (cReqSchema1, cReqSchema2) = do ctId_ <- maybeFirstRow fromOnly $ DB.query @@ -484,7 +488,7 @@ getContactWithoutConnViaAddress db user@User {userId} (cReqSchema1, cReqSchema2) WHERE cp.user_id = ? AND cp.contact_link IN (?,?) AND c.connection_id IS NULL |] (userId, cReqSchema1, cReqSchema2) - maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getContact db user) ctId_ + maybe (pure Nothing) (fmap eitherToMaybe . runExceptT . getContact db vr user) ctId_ updateUserAddressAutoAccept :: DB.Connection -> User -> Maybe AutoAccept -> ExceptT StoreError IO UserContactLink updateUserAddressAutoAccept db user@User {userId} autoAccept = do diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index e97ff9fe58..fd628d09ee 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -4,6 +4,7 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} {-# LANGUAGE QuasiQuotes #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE ScopedTypeVariables #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeOperators #-} @@ -36,6 +37,8 @@ import Simplex.Messaging.Agent.Protocol (ConnId, UserId) import Simplex.Messaging.Agent.Store.SQLite (firstRow, maybeFirstRow) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Crypto as C +import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport (..)) +import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Parsers (dropPrefix, sumTypeJSON) import Simplex.Messaging.Protocol (SubscriptionMode (..)) import Simplex.Messaging.Util (allFinally) @@ -92,11 +95,14 @@ data StoreError | SEUniqueID | SELargeMsg | SEInternalError {message :: String} - | SEBadChatItem {itemId :: ChatItemId} + | SEDBException {message :: String} + | SEDBBusyError {message :: String} + | SEBadChatItem {itemId :: ChatItemId, itemTs :: Maybe ChatItemTs} | SEChatItemNotFound {itemId :: ChatItemId} | SEChatItemNotFoundByText {text :: Text} | SEChatItemSharedMsgIdNotFound {sharedMsgId :: SharedMsgId} | SEChatItemNotFoundByFileId {fileId :: FileTransferId} + | SEChatItemNotFoundByContactId {contactId :: ContactId} | SEChatItemNotFoundByGroupId {groupId :: GroupId} | SEProfileNotFound {profileId :: Int64} | SEDuplicateGroupLink {groupInfo :: GroupInfo} @@ -147,17 +153,38 @@ toFileInfo (fileId, fileStatus, filePath) = CIFileInfo {fileId, fileStatus, file type EntityIdsRow = (Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64, Maybe Int64) -type ConnectionRow = (Int64, ConnId, Int, Maybe Int64, Maybe Int64, Bool, Maybe GroupLinkId, Maybe Int64, ConnStatus, ConnType, Bool, LocalAlias) :. EntityIdsRow :. (UTCTime, Maybe Text, Maybe UTCTime, Int, Version, Version) +type ConnectionRow = (Int64, ConnId, Int, Maybe Int64, Maybe Int64, Bool, Maybe GroupLinkId, Maybe Int64, ConnStatus, ConnType, Bool, LocalAlias) :. EntityIdsRow :. (UTCTime, Maybe Text, Maybe UTCTime, PQSupport, PQEncryption, Maybe PQEncryption, Maybe PQEncryption, Int, Maybe VersionChat, VersionChat, VersionChat) -type MaybeConnectionRow = (Maybe Int64, Maybe ConnId, Maybe Int, Maybe Int64, Maybe Int64, Maybe Bool, Maybe GroupLinkId, Maybe Int64, Maybe ConnStatus, Maybe ConnType, Maybe Bool, Maybe LocalAlias) :. EntityIdsRow :. (Maybe UTCTime, Maybe Text, Maybe UTCTime, Maybe Int, Maybe Version, Maybe Version) +type MaybeConnectionRow = (Maybe Int64, Maybe ConnId, Maybe Int, Maybe Int64, Maybe Int64, Maybe Bool, Maybe GroupLinkId, Maybe Int64, Maybe ConnStatus, Maybe ConnType, Maybe Bool, Maybe LocalAlias) :. EntityIdsRow :. (Maybe UTCTime, Maybe Text, Maybe UTCTime, Maybe PQSupport, Maybe PQEncryption, Maybe PQEncryption, Maybe PQEncryption, Maybe Int, Maybe VersionChat, Maybe VersionChat, Maybe VersionChat) -toConnection :: ConnectionRow -> Connection -toConnection ((connId, acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, contactConnInitiated, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_, authErrCounter, minVer, maxVer)) = - let entityId = entityId_ connType - connectionCode = SecurityCode <$> code_ <*> verifiedAt_ - peerChatVRange = JVersionRange $ fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer - in Connection {connId, agentConnId = AgentConnId acId, peerChatVRange, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, contactConnInitiated, localAlias, entityId, connectionCode, authErrCounter, createdAt} +toConnection :: (PQSupport -> VersionRangeChat) -> ConnectionRow -> Connection +toConnection vr ((connId, acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, contactConnInitiated, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_, pqSupport, pqEncryption, pqSndEnabled, pqRcvEnabled, authErrCounter, chatV, minVer, maxVer)) = + Connection + { connId, + agentConnId = AgentConnId acId, + connChatVersion = fromMaybe (vr pqSupport `peerConnChatVersion` peerChatVRange) chatV, + peerChatVRange = peerChatVRange, + connLevel, + viaContact, + viaUserContactLink, + viaGroupLink, + groupLinkId, + customUserProfileId, + connStatus, + connType, + contactConnInitiated, + localAlias, + entityId = entityId_ connType, + connectionCode = SecurityCode <$> code_ <*> verifiedAt_, + pqSupport, + pqEncryption, + pqSndEnabled, + pqRcvEnabled, + authErrCounter, + createdAt + } where + peerChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer entityId_ :: ConnType -> Maybe Int64 entityId_ ConnContact = contactId entityId_ ConnMember = groupMemberId @@ -165,13 +192,13 @@ toConnection ((connId, acId, connLevel, viaContact, viaUserContactLink, viaGroup entityId_ ConnSndFile = sndFileId entityId_ ConnUserContact = userContactLinkId -toMaybeConnection :: MaybeConnectionRow -> Maybe Connection -toMaybeConnection ((Just connId, Just agentConnId, Just connLevel, viaContact, viaUserContactLink, Just viaGroupLink, groupLinkId, customUserProfileId, Just connStatus, Just connType, Just contactConnInitiated, Just localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (Just createdAt, code_, verifiedAt_, Just authErrCounter, Just minVer, Just maxVer)) = - Just $ toConnection ((connId, agentConnId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, contactConnInitiated, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_, authErrCounter, minVer, maxVer)) -toMaybeConnection _ = Nothing +toMaybeConnection :: (PQSupport -> VersionRangeChat) -> MaybeConnectionRow -> Maybe Connection +toMaybeConnection vr ((Just connId, Just agentConnId, Just connLevel, viaContact, viaUserContactLink, Just viaGroupLink, groupLinkId, customUserProfileId, Just connStatus, Just connType, Just contactConnInitiated, Just localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (Just createdAt, code_, verifiedAt_, Just pqSupport, Just pqEncryption, pqSndEnabled_, pqRcvEnabled_, Just authErrCounter, connChatVersion, Just minVer, Just maxVer)) = + Just $ toConnection vr ((connId, agentConnId, connLevel, viaContact, viaUserContactLink, viaGroupLink, groupLinkId, customUserProfileId, connStatus, connType, contactConnInitiated, localAlias) :. (contactId, groupMemberId, sndFileId, rcvFileId, userContactLinkId) :. (createdAt, code_, verifiedAt_, pqSupport, pqEncryption, pqSndEnabled_, pqRcvEnabled_, authErrCounter, connChatVersion, minVer, maxVer)) +toMaybeConnection _ _ = Nothing -createConnection_ :: DB.Connection -> UserId -> ConnType -> Maybe Int64 -> ConnId -> VersionRange -> Maybe ContactId -> Maybe Int64 -> Maybe ProfileId -> Int -> UTCTime -> SubscriptionMode -> IO Connection -createConnection_ db userId connType entityId acId peerChatVRange@(VersionRange minV maxV) viaContact viaUserContactLink customUserProfileId connLevel currentTs subMode = do +createConnection_ :: DB.Connection -> UserId -> ConnType -> Maybe Int64 -> ConnId -> VersionChat -> VersionRangeChat -> Maybe ContactId -> Maybe Int64 -> Maybe ProfileId -> Int -> UTCTime -> SubscriptionMode -> PQSupport -> IO Connection +createConnection_ db userId connType entityId acId connChatVersion peerChatVRange@(VersionRange minV maxV) viaContact viaUserContactLink customUserProfileId connLevel currentTs subMode pqSup = do viaLinkGroupId :: Maybe Int64 <- fmap join . forM viaUserContactLink $ \ucLinkId -> maybeFirstRow fromOnly $ DB.query db "SELECT group_id FROM user_contact_links WHERE user_id = ? AND user_contact_link_id = ? AND group_id IS NOT NULL" (userId, ucLinkId) let viaGroupLink = isJust viaLinkGroupId @@ -181,15 +208,39 @@ createConnection_ db userId connType entityId acId peerChatVRange@(VersionRange INSERT INTO connections ( user_id, agent_conn_id, conn_level, via_contact, via_user_contact_link, via_group_link, custom_user_profile_id, conn_status, conn_type, contact_id, group_member_id, snd_file_id, rcv_file_id, user_contact_link_id, created_at, updated_at, - peer_chat_min_version, peer_chat_max_version, to_subscribe - ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) + conn_chat_version, peer_chat_min_version, peer_chat_max_version, to_subscribe, pq_support, pq_encryption + ) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) |] ( (userId, acId, connLevel, viaContact, viaUserContactLink, viaGroupLink, customUserProfileId, ConnNew, connType) :. (ent ConnContact, ent ConnMember, ent ConnSndFile, ent ConnRcvFile, ent ConnUserContact, currentTs, currentTs) - :. (minV, maxV, subMode == SMOnlyCreate) + :. (connChatVersion, minV, maxV, subMode == SMOnlyCreate, pqSup, pqSup) ) connId <- insertedRowId db - pure Connection {connId, agentConnId = AgentConnId acId, peerChatVRange = JVersionRange peerChatVRange, connType, contactConnInitiated = False, entityId, viaContact, viaUserContactLink, viaGroupLink, groupLinkId = Nothing, customUserProfileId, connLevel, connStatus = ConnNew, localAlias = "", createdAt = currentTs, connectionCode = Nothing, authErrCounter = 0} + pure + Connection + { connId, + agentConnId = AgentConnId acId, + connChatVersion, + peerChatVRange, + connType, + contactConnInitiated = False, + entityId, + viaContact, + viaUserContactLink, + viaGroupLink, + groupLinkId = Nothing, + customUserProfileId, + connLevel, + connStatus = ConnNew, + localAlias = "", + createdAt = currentTs, + connectionCode = Nothing, + pqSupport = pqSup, + pqEncryption = CR.pqSupportToEnc pqSup, + pqSndEnabled = Nothing, + pqRcvEnabled = Nothing, + authErrCounter = 0 + } where ent ct = if connType == ct then entityId else Nothing @@ -204,18 +255,62 @@ createIncognitoProfile_ db userId createdAt Profile {displayName, fullName, imag (displayName, fullName, image, userId, Just True, createdAt, createdAt) insertedRowId db -setPeerChatVRange :: DB.Connection -> Int64 -> VersionRange -> IO () -setPeerChatVRange db connId (VersionRange minVer maxVer) = +updateConnSupportPQ :: DB.Connection -> Int64 -> PQSupport -> PQEncryption -> IO () +updateConnSupportPQ db connId pqSup pqEnc = DB.execute db [sql| UPDATE connections - SET peer_chat_min_version = ?, peer_chat_max_version = ? + SET pq_support = ?, pq_encryption = ? WHERE connection_id = ? |] - (minVer, maxVer, connId) + (pqSup, pqEnc, connId) -setMemberChatVRange :: DB.Connection -> GroupMemberId -> VersionRange -> IO () +updateConnPQSndEnabled :: DB.Connection -> Int64 -> PQEncryption -> IO () +updateConnPQSndEnabled db connId pqSndEnabled = + DB.execute + db + [sql| + UPDATE connections + SET pq_snd_enabled = ? + WHERE connection_id = ? + |] + (pqSndEnabled, connId) + +updateConnPQRcvEnabled :: DB.Connection -> Int64 -> PQEncryption -> IO () +updateConnPQRcvEnabled db connId pqRcvEnabled = + DB.execute + db + [sql| + UPDATE connections + SET pq_rcv_enabled = ? + WHERE connection_id = ? + |] + (pqRcvEnabled, connId) + +updateConnPQEnabledCON :: DB.Connection -> Int64 -> PQEncryption -> IO () +updateConnPQEnabledCON db connId pqEnabled = + DB.execute + db + [sql| + UPDATE connections + SET pq_snd_enabled = ?, pq_rcv_enabled = ? + WHERE connection_id = ? + |] + (pqEnabled, pqEnabled, connId) + +setPeerChatVRange :: DB.Connection -> Int64 -> VersionChat -> VersionRangeChat -> IO () +setPeerChatVRange db connId chatV (VersionRange minVer maxVer) = + DB.execute + db + [sql| + UPDATE connections + SET conn_chat_version = ?, peer_chat_min_version = ?, peer_chat_max_version = ? + WHERE connection_id = ? + |] + (chatV, minVer, maxVer, connId) + +setMemberChatVRange :: DB.Connection -> GroupMemberId -> VersionRangeChat -> IO () setMemberChatVRange db mId (VersionRange minVer maxVer) = DB.execute db @@ -278,10 +373,10 @@ deleteUnusedIncognitoProfileById_ db User {userId} profileId = type ContactRow = (ContactId, ProfileId, ContactName, Maybe Int64, ContactName, Text, Maybe ImageData, Maybe ConnReqContact, LocalAlias, Bool, ContactStatus) :. (Maybe MsgFilter, Maybe Bool, Bool, Maybe Preferences, Preferences, UTCTime, UTCTime, Maybe UTCTime, Maybe GroupMemberId, Bool) -toContact :: User -> ContactRow :. MaybeConnectionRow -> Contact -toContact user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, favorite, preferences, userPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent)) :. connRow) = +toContact :: (PQSupport -> VersionRangeChat) -> User -> ContactRow :. MaybeConnectionRow -> Contact +toContact vr user (((contactId, profileId, localDisplayName, viaGroup, displayName, fullName, image, contactLink, localAlias, contactUsed, contactStatus) :. (enableNtfs_, sendRcpts, favorite, preferences, userPreferences, createdAt, updatedAt, chatTs, contactGroupMemberId, contactGrpInvSent)) :. connRow) = let profile = LocalProfile {profileId, displayName, fullName, image, contactLink, preferences, localAlias} - activeConn = toMaybeConnection connRow + activeConn = toMaybeConnection vr connRow chatSettings = ChatSettings {enableNtfs = fromMaybe MFAll enableNtfs_, sendRcpts, favorite} incognito = maybe False connIncognito activeConn mergedPreferences = contactUserPreferences user userPreferences preferences incognito @@ -302,13 +397,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, Maybe Preferences, UTCTime, UTCTime, Version, Version) +type ContactRequestRow = (Int64, ContactName, AgentInvId, 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, preferences, createdAt, updatedAt, minVer, maxVer)) = do +toContactRequest ((contactRequestId, localDisplayName, agentInvitationId, userContactLinkId, agentContactConnId, profileId, displayName, fullName, image, contactLink) :. (xContactId, pqSupport, preferences, createdAt, updatedAt, minVer, maxVer)) = do let profile = Profile {displayName, fullName, image, contactLink, preferences} - cReqChatVRange = JVersionRange $ fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer - in UserContactRequest {contactRequestId, agentInvitationId, userContactLinkId, agentContactConnId, cReqChatVRange, localDisplayName, profileId, profile, xContactId, createdAt, updatedAt} + cReqChatVRange = fromMaybe (versionToRange maxVer) $ safeVersionRange minVer maxVer + in UserContactRequest {contactRequestId, agentInvitationId, userContactLinkId, agentContactConnId, cReqChatVRange, localDisplayName, profileId, profile, xContactId, pqSupport, createdAt, updatedAt} userQuery :: Query userQuery = diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index e65e1a916f..f16913439d 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -23,7 +23,7 @@ module Simplex.Chat.Types where import Crypto.Number.Serialize (os2ip) -import Data.Aeson (FromJSON (..), ToJSON (..), (.:), (.=)) +import Data.Aeson (FromJSON (..), ToJSON (..)) import qualified Data.Aeson as J import qualified Data.Aeson.Encoding as JE import qualified Data.Aeson.TH as JQ @@ -38,6 +38,7 @@ import qualified Data.Text as T import Data.Text.Encoding (encodeUtf8) import Data.Time.Clock (UTCTime) import Data.Typeable (Typeable) +import Data.Word (Word16) import Database.SQLite.Simple (ResultError (..), SQLData (..)) import Database.SQLite.Simple.FromField (FromField (..), returnError) import Database.SQLite.Simple.Internal (Field (..)) @@ -46,13 +47,15 @@ import Database.SQLite.Simple.ToField (ToField (..)) import Simplex.Chat.Types.Preferences import Simplex.Chat.Types.Util import Simplex.FileTransfer.Description (FileDigest) -import Simplex.Messaging.Agent.Protocol (ACommandTag (..), ACorrId, AParty (..), APartyCmdTag (..), ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId, SAEntity (..), UserId) +import Simplex.Messaging.Agent.Protocol (ACommandTag (..), ACorrId, AParty (..), APartyCmdTag (..), ConnId, ConnectionMode (..), ConnectionRequestUri, InvitationId, RcvFileId, SAEntity (..), SndFileId, UserId) import Simplex.Messaging.Crypto.File (CryptoFileArgs (..)) +import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, fromTextField_, sumTypeJSON, taggedObjectJSON) import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI) import Simplex.Messaging.Util (safeDecodeUtf8, (<$?>)) import Simplex.Messaging.Version +import Simplex.Messaging.Version.Internal class IsContact a where contactId' :: a -> ContactId @@ -211,6 +214,9 @@ contactDeleted Contact {contactStatus} = contactStatus == CSDeleted contactSecurityCode :: Contact -> Maybe SecurityCode contactSecurityCode Contact {activeConn} = connectionCode =<< activeConn +contactPQEnabled :: Contact -> PQEncryption +contactPQEnabled Contact {activeConn} = maybe PQEncOff connPQEnabled activeConn + data ContactStatus = CSActive | CSDeleted -- contact deleted by contact @@ -272,13 +278,14 @@ data UserContactRequest = UserContactRequest agentInvitationId :: AgentInvId, userContactLinkId :: Int64, agentContactConnId :: AgentConnId, -- connection id of user contact - cReqChatVRange :: JVersionRange, + cReqChatVRange :: VersionRangeChat, localDisplayName :: ContactName, profileId :: Int64, profile :: Profile, createdAt :: UTCTime, updatedAt :: UTCTime, - xContactId :: Maybe XContactId + xContactId :: Maybe XContactId, + pqSupport :: PQSupport } deriving (Eq, Show) @@ -563,7 +570,8 @@ data GroupInvitation = GroupInvitation invitedMember :: MemberIdRole, connRequest :: ConnReqInvitation, groupProfile :: GroupProfile, - groupLinkId :: Maybe GroupLinkId + groupLinkId :: Maybe GroupLinkId, + groupSize :: Maybe Int } deriving (Eq, Show) @@ -571,7 +579,8 @@ data GroupLinkInvitation = GroupLinkInvitation { fromMember :: MemberIdRole, fromMemberName :: ContactName, invitedMember :: MemberIdRole, - groupProfile :: GroupProfile + groupProfile :: GroupProfile, + groupSize :: Maybe Int } deriving (Eq, Show) @@ -600,7 +609,7 @@ memberInfo GroupMember {memberId, memberRole, memberProfile, activeConn} = MemberInfo { memberId, memberRole, - v = ChatVersionRange . fromJVersionRange . peerChatVRange <$> activeConn, + v = ChatVersionRange . peerChatVRange <$> activeConn, profile = redactedMemberProfile $ fromLocalProfile memberProfile } @@ -682,7 +691,7 @@ data GroupMember = GroupMember -- member chat protocol version range; if member has active connection, its version range is preferred; -- for membership current supportedChatVRange is set, it's not updated on protocol version increase in database, -- but it's correctly set on read (see toGroupInfo) - memberChatVRange :: JVersionRange + memberChatVRange :: VersionRangeChat } deriving (Eq, Show) @@ -699,11 +708,13 @@ memberConn GroupMember {activeConn} = activeConn memberConnId :: GroupMember -> Maybe ConnId memberConnId GroupMember {activeConn} = aConnId <$> activeConn -memberChatVRange' :: GroupMember -> VersionRange -memberChatVRange' GroupMember {activeConn, memberChatVRange} = - fromJVersionRange $ case activeConn of - Just Connection {peerChatVRange} -> peerChatVRange - Nothing -> memberChatVRange +memberChatVRange' :: GroupMember -> VersionRangeChat +memberChatVRange' GroupMember {activeConn, memberChatVRange} = case activeConn of + Just Connection {peerChatVRange} -> peerChatVRange + Nothing -> memberChatVRange + +supportsVersion :: GroupMember -> VersionChat -> Bool +supportsVersion m v = maxVersion (memberChatVRange' m) >= v groupMemberId' :: GroupMember -> GroupMemberId groupMemberId' GroupMember {groupMemberId} = groupMemberId @@ -1142,7 +1153,7 @@ instance FromField AgentConnId where fromField f = AgentConnId <$> fromField f instance ToField AgentConnId where toField (AgentConnId m) = toField m -newtype AgentSndFileId = AgentSndFileId ConnId +newtype AgentSndFileId = AgentSndFileId SndFileId deriving (Eq, Show) instance StrEncoding AgentSndFileId where @@ -1161,7 +1172,7 @@ instance FromField AgentSndFileId where fromField f = AgentSndFileId <$> fromFie instance ToField AgentSndFileId where toField (AgentSndFileId m) = toField m -newtype AgentRcvFileId = AgentRcvFileId ConnId +newtype AgentRcvFileId = AgentRcvFileId RcvFileId deriving (Eq, Show) instance StrEncoding AgentRcvFileId where @@ -1210,6 +1221,7 @@ data FileTransfer data FileTransferMeta = FileTransferMeta { fileId :: FileTransferId, xftpSndFile :: Maybe XFTPSndFile, + xftpRedirectFor :: Maybe FileTransferId, fileName :: String, filePath :: String, fileSize :: Integer, @@ -1279,7 +1291,8 @@ type ConnReqContact = ConnectionRequestUri 'CMContact data Connection = Connection { connId :: Int64, agentConnId :: AgentConnId, - peerChatVRange :: JVersionRange, + connChatVersion :: VersionChat, + peerChatVRange :: VersionRangeChat, connLevel :: Int, viaContact :: Maybe Int64, -- group member contact ID, if not direct connection viaUserContactLink :: Maybe Int64, -- user contact link ID, if connected via "user address" @@ -1292,6 +1305,10 @@ data Connection = Connection localAlias :: Text, entityId :: Maybe Int64, -- contact, group member, file ID or user contact ID connectionCode :: Maybe SecurityCode, + pqSupport :: PQSupport, + pqEncryption :: PQEncryption, + pqSndEnabled :: Maybe PQEncryption, + pqRcvEnabled :: Maybe PQEncryption, authErrCounter :: Int, createdAt :: UTCTime } @@ -1326,6 +1343,10 @@ aConnId Connection {agentConnId = AgentConnId cId} = cId connIncognito :: Connection -> Bool connIncognito Connection {customUserProfileId} = isJust customUserProfileId +connPQEnabled :: Connection -> PQEncryption +connPQEnabled Connection {pqSndEnabled = Just (PQEncryption s), pqRcvEnabled = Just (PQEncryption r)} = PQEncryption $ s && r +connPQEnabled _ = PQEncOff + data PendingContactConnection = PendingContactConnection { pccConnId :: Int64, pccAgentConnId :: AgentConnId, @@ -1614,10 +1635,32 @@ data ServerCfg p = ServerCfg } deriving (Show) -newtype ChatVersionRange = ChatVersionRange {fromChatVRange :: VersionRange} deriving (Eq, Show) +data ChatVersion -chatInitialVRange :: VersionRange -chatInitialVRange = versionToRange 1 +instance VersionScope ChatVersion + +type VersionChat = Version ChatVersion + +type VersionRangeChat = VersionRange ChatVersion + +pattern VersionChat :: Word16 -> VersionChat +pattern VersionChat v = Version v + +-- this newtype exists to have a concise JSON encoding of version ranges in chat protocol messages in the form of "1-2" or just "1" +newtype ChatVersionRange = ChatVersionRange {fromChatVRange :: VersionRangeChat} deriving (Eq, Show) + +-- TODO v6.0 review +peerConnChatVersion :: VersionRangeChat -> VersionRangeChat -> VersionChat +peerConnChatVersion _local@(VersionRange lmin lmax) _peer@(VersionRange rmin rmax) + | lmin <= rmax && rmin <= lmax = min lmax rmax -- compatible + | rmin > lmax = rmin + | otherwise = rmax + +initialChatVersion :: VersionChat +initialChatVersion = VersionChat 1 + +chatInitialVRange :: VersionRangeChat +chatInitialVRange = versionToRange initialChatVersion instance FromJSON ChatVersionRange where parseJSON v = ChatVersionRange <$> strParseJSON "ChatVersionRange" v @@ -1626,18 +1669,6 @@ instance ToJSON ChatVersionRange where toJSON (ChatVersionRange vr) = strToJSON vr toEncoding (ChatVersionRange vr) = strToJEncoding vr -newtype JVersionRange = JVersionRange {fromJVersionRange :: VersionRange} deriving (Eq, Show) - -instance FromJSON JVersionRange where - parseJSON = J.withObject "JVersionRange" $ \o -> do - minv <- o .: "minVersion" - maxv <- o .: "maxVersion" - maybe (fail "bad version range") (pure . JVersionRange) $ safeVersionRange minv maxv - -instance ToJSON JVersionRange where - toJSON (JVersionRange (VersionRange minV maxV)) = J.object ["minVersion" .= minV, "maxVersion" .= maxV] - toEncoding (JVersionRange (VersionRange minV maxV)) = J.pairs $ "minVersion" .= minV <> "maxVersion" .= maxV - $(JQ.deriveJSON defaultJSON ''UserContact) $(JQ.deriveJSON defaultJSON ''Profile) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 05c90696b4..50dc151aa4 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -49,13 +49,14 @@ import Simplex.Chat.Store (AutoAccept (..), StoreError (..), UserContactLink (.. import Simplex.Chat.Styled import Simplex.Chat.Types import Simplex.Chat.Types.Preferences -import qualified Simplex.FileTransfer.Protocol as XFTP +import qualified Simplex.FileTransfer.Transport as XFTPTransport import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestStep (..), SubscriptionsInfo (..)) import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..)) import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) +import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Encoding import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON) @@ -198,19 +199,27 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRGroupMemberUpdated {} -> [] CRContactsMerged u intoCt mergedCt ct' -> ttyUser u $ viewContactsMerged intoCt mergedCt ct' CRReceivedContactRequest u UserContactRequest {localDisplayName = c, profile} -> ttyUser u $ viewReceivedContactRequest c profile + CRRcvStandaloneFileCreated u ft -> ttyUser u $ receivingFileStandalone "started" ft CRRcvFileStart u ci -> ttyUser u $ receivingFile_' hu testView "started" ci CRRcvFileComplete u ci -> ttyUser u $ receivingFile_' hu testView "completed" ci + CRRcvStandaloneFileComplete u _ ft -> ttyUser u $ receivingFileStandalone "completed" ft CRRcvFileSndCancelled u _ ft -> ttyUser u $ viewRcvFileSndCancelled ft - CRRcvFileError u ci e -> ttyUser u $ receivingFile_' hu testView "error" ci <> [sShow e] + CRRcvFileError u (Just ci) e _ -> ttyUser u $ receivingFile_' hu testView "error" ci <> [sShow e] + CRRcvFileError u Nothing e ft -> ttyUser u $ receivingFileStandalone "error" ft <> [sShow e] CRSndFileStart u _ ft -> ttyUser u $ sendingFile_ "started" ft CRSndFileComplete u _ ft -> ttyUser u $ sendingFile_ "completed" ft + CRSndStandaloneFileCreated u ft -> ttyUser u $ uploadingFileStandalone "started" ft CRSndFileStartXFTP {} -> [] CRSndFileProgressXFTP {} -> [] + CRSndFileRedirectStartXFTP u ft ftRedirect -> ttyUser u $ standaloneUploadRedirect ft ftRedirect + CRSndStandaloneFileComplete u ft uris -> ttyUser u $ standaloneUploadComplete ft uris CRSndFileCompleteXFTP u ci _ -> ttyUser u $ uploadingFile "completed" ci CRSndFileCancelledXFTP {} -> [] - CRSndFileError u ci -> ttyUser u $ uploadingFile "error" ci + CRSndFileError u Nothing ft e -> ttyUser u $ uploadingFileStandalone "error" ft <> [plain e] + CRSndFileError u (Just ci) _ e -> ttyUser u $ uploadingFile "error" ci <> [plain e] CRSndFileRcvCancelled u _ ft@SndFileTransfer {recipientDisplayName = c} -> ttyUser u [ttyContact c <> " cancelled receiving " <> sndFile ft] + CRStandaloneFileInfo info_ -> maybe ["no file information in URI"] (\j -> [plain . LB.toStrict $ J.encode j]) info_ CRContactConnecting u _ -> ttyUser u [] CRContactConnected u ct userCustomProfile -> ttyUser u $ viewContactConnected ct userCustomProfile testView CRContactAnotherClient u c -> ttyUser u [ttyContact' c <> ": contact is connected to another client"] @@ -283,7 +292,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRUserContactLinkSubError e -> ["user address error: " <> sShow e, "to delete your address: " <> highlight' "/da"] CRContactConnectionDeleted u PendingContactConnection {pccConnId} -> ttyUser u ["connection :" <> sShow pccConnId <> " deleted"] CRNtfTokenStatus status -> ["device token status: " <> plain (smpEncode status)] - CRNtfToken _ status mode -> ["device token status: " <> plain (smpEncode status) <> ", notifications mode: " <> plain (strEncode mode)] + CRNtfToken _ status mode srv -> ["device token status: " <> plain (smpEncode status) <> ", notifications mode: " <> plain (strEncode mode) <> ", server: " <> sShow srv] CRNtfMessages {} -> [] CRNtfMessage {} -> [] CRCurrentRemoteHost rhi_ -> @@ -333,6 +342,8 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRRemoteCtrlConnected RemoteCtrlInfo {remoteCtrlId = rcId, ctrlDeviceName} -> ["remote controller " <> sShow rcId <> " session started with " <> plain ctrlDeviceName] CRRemoteCtrlStopped {} -> ["remote controller stopped"] + CRContactPQAllowed u c (CR.PQEncryption pqOn) -> ttyUser u [ttyContact' c <> ": enable " <> (if pqOn then "quantum resistant" else "standard") <> " end-to-end encryption"] + CRContactPQEnabled u c (CR.PQEncryption pqOn) -> ttyUser u [ttyContact' c <> ": " <> (if pqOn then "quantum resistant" else "standard") <> " end-to-end encryption enabled"] CRSQLResult rows -> map plain rows CRSlowSQLQueries {chatQueries, agentQueries} -> let viewQuery SlowSQLQuery {query, queryStats = SlowQueryStats {count, timeMax, timeAvg}} = @@ -378,6 +389,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRChatError u e -> ttyUser' u $ viewChatError logLevel testView e CRChatErrors u errs -> ttyUser' u $ concatMap (viewChatError logLevel testView) errs CRArchiveImported archiveErrs -> if null archiveErrs then ["ok"] else ["archive import errors: " <> plain (show archiveErrs)] + CRAppSettings as -> ["app settings: " <> plain (LB.unpack $ J.encode as)] CRTimedAction _ _ -> [] where ttyUser :: User -> [StyledString] -> [StyledString] @@ -1125,7 +1137,7 @@ viewServerTestResult (AProtoServerWithAuth p _) = \case Just ProtocolTestFailure {testStep, testError} -> result <> [pName <> " server requires authorization to create queues, check password" | testStep == TSCreateQueue && testError == SMP SMP.AUTH] - <> [pName <> " server requires authorization to upload files, check password" | testStep == TSCreateFile && testError == XFTP XFTP.AUTH] + <> [pName <> " server requires authorization to upload files, check password" | testStep == TSCreateFile && testError == XFTP XFTPTransport.AUTH] <> ["Possibly, certificate fingerprint in " <> pName <> " server address is incorrect" | testStep == TSConnect && brokerErr] where result = [pName <> " server test failed at " <> plain (drop 2 $ show testStep) <> ", error: " <> plain (strEncode testError)] @@ -1165,6 +1177,7 @@ viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, conta incognitoProfile <> ["alias: " <> plain localAlias | localAlias /= ""] <> [viewConnectionVerified (contactSecurityCode ct)] + <> ["quantum resistant end-to-end encryption" | contactPQEnabled ct == CR.PQEncOn] <> maybe [] (\ac -> [viewPeerChatVRange (peerChatVRange ac)]) activeConn viewGroupInfo :: GroupInfo -> GroupSummary -> [StyledString] @@ -1188,8 +1201,8 @@ viewConnectionVerified :: Maybe SecurityCode -> StyledString viewConnectionVerified (Just _) = "connection verified" -- TODO show verification time? viewConnectionVerified _ = "connection not verified, use " <> highlight' "/code" <> " command to see security code" -viewPeerChatVRange :: JVersionRange -> StyledString -viewPeerChatVRange (JVersionRange (VersionRange minVer maxVer)) = "peer chat protocol version range: (" <> sShow minVer <> ", " <> sShow maxVer <> ")" +viewPeerChatVRange :: VersionRangeChat -> StyledString +viewPeerChatVRange (VersionRange minVer maxVer) = "peer chat protocol version range: (" <> sShow minVer <> ", " <> sShow maxVer <> ")" viewConnectionStats :: ConnectionStats -> [StyledString] viewConnectionStats ConnectionStats {rcvQueuesInfo, sndQueuesInfo} = @@ -1558,11 +1571,26 @@ sendingFile_ status ft@SndFileTransfer {recipientDisplayName = c} = [status <> " sending " <> sndFile ft <> " to " <> ttyContact c] uploadingFile :: StyledString -> AChatItem -> [StyledString] -uploadingFile status (AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectSnd}) = - [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyContact c] -uploadingFile status (AChatItem _ _ (GroupChat g) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupSnd}) = - [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyGroup' g] -uploadingFile status _ = [status <> " uploading file"] -- shouldn't happen +uploadingFile status = \case + AChatItem _ _ (DirectChat Contact {localDisplayName = c}) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIDirectSnd} -> + [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyContact c] + AChatItem _ _ (GroupChat g) ChatItem {file = Just CIFile {fileId, fileName}, chatDir = CIGroupSnd} -> + [status <> " uploading " <> fileTransferStr fileId fileName <> " for " <> ttyGroup' g] + _ -> [status <> " uploading file"] + +uploadingFileStandalone :: StyledString -> FileTransferMeta -> [StyledString] +uploadingFileStandalone status FileTransferMeta {fileId, fileName} = [status <> " standalone uploading " <> fileTransferStr fileId fileName] + +standaloneUploadRedirect :: FileTransferMeta -> FileTransferMeta -> [StyledString] +standaloneUploadRedirect FileTransferMeta {fileId, fileName} FileTransferMeta {fileId = redirectId} = + [fileTransferStr fileId fileName <> " uploaded, preparing redirect file " <> sShow redirectId] + +standaloneUploadComplete :: FileTransferMeta -> [Text] -> [StyledString] +standaloneUploadComplete FileTransferMeta {fileId, fileName} = \case + [] -> [fileTransferStr fileId fileName <> " upload complete."] + uris -> + fileTransferStr fileId fileName <> " upload complete. download with:" + : map plain uris sndFile :: SndFileTransfer -> StyledString sndFile SndFileTransfer {fileId, fileName} = fileTransferStr fileId fileName @@ -1608,7 +1636,11 @@ receivingFile_' hu testView status (AChatItem _ _ chat ChatItem {file = Just CIF highlight ("/get remote file " <> show rhId <> " " <> LB.unpack (J.encode RemoteFile {userId, fileId, sent = False, fileSource = f})) ] _ -> [] -receivingFile_' _ _ status _ = [plain status <> " receiving file"] -- shouldn't happen +receivingFile_' _ _ status _ = [plain status <> " receiving file"] + +receivingFileStandalone :: String -> RcvFileTransfer -> [StyledString] +receivingFileStandalone status RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}} = + [plain status <> " standalone receiving " <> fileTransferStr fileId fileName] viewLocalFile :: StyledString -> CIFile d -> CurrentTime -> TimeZone -> CIMeta c d -> [StyledString] viewLocalFile to CIFile {fileId, fileSource} ts tz = case fileSource of @@ -1627,7 +1659,7 @@ fileFrom _ _ = "" receivingFile_ :: StyledString -> RcvFileTransfer -> [StyledString] receivingFile_ status ft@RcvFileTransfer {senderDisplayName = c} = - [status <> " receiving " <> rcvFile ft <> " from " <> ttyContact c] + [status <> " receiving " <> rcvFile ft <> if c == "" then "" else " from " <> ttyContact c] rcvFile :: RcvFileTransfer -> StyledString rcvFile RcvFileTransfer {fileId, fileInvitation = FileInvitation {fileName}} = fileTransferStr fileId fileName diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 0240648603..8e9d11d91f 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -3,7 +3,9 @@ {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RankNTypes #-} +{-# LANGUAGE TupleSections #-} {-# LANGUAGE TypeApplications #-} {-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} @@ -26,24 +28,30 @@ import Simplex.Chat import Simplex.Chat.Controller (ChatCommand (..), ChatConfig (..), ChatController (..), ChatDatabase (..), ChatLogLevel (..)) import Simplex.Chat.Core import Simplex.Chat.Options +import Simplex.Chat.Protocol (currentChatVersion, pqEncryptionCompressionVersion) import Simplex.Chat.Store import Simplex.Chat.Store.Profiles import Simplex.Chat.Terminal import Simplex.Chat.Terminal.Output (newChatTerminal) -import Simplex.Chat.Types (AgentUserId (..), Profile, User (..)) +import Simplex.Chat.Types import Simplex.FileTransfer.Description (kb, mb) import Simplex.FileTransfer.Server (runXFTPServerBlocking) import Simplex.FileTransfer.Server.Env (XFTPServerConfig (..), defaultFileExpiration) +import Simplex.Messaging.Agent (disposeAgentClient) import Simplex.Messaging.Agent.Env.SQLite +import Simplex.Messaging.Agent.Protocol (currentSMPAgentVersion, duplexHandshakeSMPAgentVersion, pqdrSMPAgentVersion, supportedSMPAgentVRange) import Simplex.Messaging.Agent.RetryInterval -import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..)) +import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), closeSQLiteStore) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import Simplex.Messaging.Client (ProtocolClientConfig (..), defaultNetworkConfig) +import Simplex.Messaging.Crypto.Ratchet (supportedE2EEncryptVRange, pattern PQSupportOff) +import qualified Simplex.Messaging.Crypto.Ratchet as CR import Simplex.Messaging.Server (runSMPServerBlocking) import Simplex.Messaging.Server.Env.STM import Simplex.Messaging.Transport import Simplex.Messaging.Transport.Server (defaultTransportServerConfig) import Simplex.Messaging.Version +import Simplex.Messaging.Version.Internal import System.Directory (createDirectoryIfMissing, removeDirectoryRecursive) import System.FilePath ((</>)) import qualified System.Terminal as C @@ -67,6 +75,7 @@ testOpts = chatCmdLog = CCLNone, chatServerPort = Nothing, optFilesFolder = Nothing, + optTempDirectory = Nothing, showReactions = True, allowInstantFiles = True, autoAcceptFileSize = 0, @@ -136,60 +145,85 @@ testCfg = testAgentCfgVPrev :: AgentConfig testAgentCfgVPrev = testAgentCfg - { smpAgentVRange = prevRange $ smpAgentVRange testAgentCfg, - smpClientVRange = prevRange $ smpClientVRange testAgentCfg, - e2eEncryptVRange = prevRange $ e2eEncryptVRange testAgentCfg, + { smpClientVRange = prevRange $ smpClientVRange testAgentCfg, + smpAgentVRange = \_ -> prevRange $ supportedSMPAgentVRange PQSupportOff, + e2eEncryptVRange = \_ -> prevRange $ supportedE2EEncryptVRange PQSupportOff, smpCfg = (smpCfg testAgentCfg) {serverVRange = prevRange $ serverVRange $ smpCfg testAgentCfg} } +testAgentCfgVNext :: AgentConfig +testAgentCfgVNext = + testAgentCfg + { smpClientVRange = nextRange $ smpClientVRange testAgentCfg, + smpAgentVRange = \_ -> mkVersionRange duplexHandshakeSMPAgentVersion $ max pqdrSMPAgentVersion currentSMPAgentVersion, + e2eEncryptVRange = \_ -> mkVersionRange CR.kdfX3DHE2EEncryptVersion $ max CR.pqRatchetE2EEncryptVersion CR.currentE2EEncryptVersion, + smpCfg = (smpCfg testAgentCfg) {serverVRange = nextRange $ serverVRange $ smpCfg testAgentCfg} + } + testAgentCfgV1 :: AgentConfig testAgentCfgV1 = testAgentCfg { smpClientVRange = v1Range, - smpAgentVRange = v1Range, - e2eEncryptVRange = v1Range, - smpCfg = (smpCfg testAgentCfg) {serverVRange = v1Range} + smpAgentVRange = \_ -> versionToRange duplexHandshakeSMPAgentVersion, + e2eEncryptVRange = \_ -> versionToRange CR.kdfX3DHE2EEncryptVersion, + smpCfg = (smpCfg testAgentCfg) {serverVRange = versionToRange batchCmdsSMPVersion} } testCfgVPrev :: ChatConfig testCfgVPrev = testCfg - { chatVRange = prevRange $ chatVRange testCfg, + { chatVRange = \_ -> prevRange $ chatVRange testCfg PQSupportOff, agentConfig = testAgentCfgVPrev } +testCfgVNext :: ChatConfig +testCfgVNext = + testCfg + { chatVRange = \_ -> mkVersionRange initialChatVersion $ max pqEncryptionCompressionVersion currentChatVersion, + agentConfig = testAgentCfgVNext + } + testCfgV1 :: ChatConfig testCfgV1 = testCfg - { chatVRange = v1Range, + { chatVRange = const v1Range, agentConfig = testAgentCfgV1 } -prevRange :: VersionRange -> VersionRange -prevRange vr = vr {maxVersion = maxVersion vr - 1} +prevRange :: VersionRange v -> VersionRange v +prevRange vr = vr {maxVersion = max (minVersion vr) (prevVersion $ maxVersion vr)} -v1Range :: VersionRange -v1Range = mkVersionRange 1 1 +nextRange :: VersionRange v -> VersionRange v +nextRange vr = vr {maxVersion = max (minVersion vr) (nextVersion $ maxVersion vr)} + +v1Range :: VersionRange v +v1Range = mkVersionRange (Version 1) (Version 1) + +prevVersion :: Version v -> Version v +prevVersion (Version v) = Version (v - 1) + +nextVersion :: Version v -> Version v +nextVersion (Version v) = Version (v + 1) testCfgCreateGroupDirect :: ChatConfig testCfgCreateGroupDirect = mkCfgCreateGroupDirect testCfg mkCfgCreateGroupDirect :: ChatConfig -> ChatConfig -mkCfgCreateGroupDirect cfg = cfg {chatVRange = groupCreateDirectVRange} +mkCfgCreateGroupDirect cfg = cfg {chatVRange = const groupCreateDirectVRange} -groupCreateDirectVRange :: VersionRange -groupCreateDirectVRange = mkVersionRange 1 1 +groupCreateDirectVRange :: VersionRangeChat +groupCreateDirectVRange = mkVersionRange (VersionChat 1) (VersionChat 1) testCfgGroupLinkViaContact :: ChatConfig testCfgGroupLinkViaContact = mkCfgGroupLinkViaContact testCfg mkCfgGroupLinkViaContact :: ChatConfig -> ChatConfig -mkCfgGroupLinkViaContact cfg = cfg {chatVRange = groupLinkViaContactVRange} +mkCfgGroupLinkViaContact cfg = cfg {chatVRange = const groupLinkViaContactVRange} -groupLinkViaContactVRange :: VersionRange -groupLinkViaContactVRange = mkVersionRange 1 2 +groupLinkViaContactVRange :: VersionRangeChat +groupLinkViaContactVRange = mkVersionRange (VersionChat 1) (VersionChat 2) createTestChat :: FilePath -> ChatConfig -> ChatOpts -> String -> Profile -> IO TestCC createTestChat tmp cfg opts@ChatOpts {coreOptions = CoreChatOpts {dbKey}} dbPrefix profile = do @@ -217,10 +251,12 @@ startTestChat_ db cfg opts user = do pure TestCC {chatController = cc, virtualTerminal = t, chatAsync, termAsync, termQ, printOutput = False} stopTestChat :: TestCC -> IO () -stopTestChat TestCC {chatController = cc, chatAsync, termAsync} = do +stopTestChat TestCC {chatController = cc@ChatController {smpAgent, chatStore}, chatAsync, termAsync} = do stopChatController cc uninterruptibleCancel termAsync uninterruptibleCancel chatAsync + liftIO $ disposeAgentClient smpAgent + closeSQLiteStore chatStore threadDelay 200000 withNewTestChat :: HasCallStack => FilePath -> String -> Profile -> (HasCallStack => TestCC -> IO a) -> IO a @@ -318,7 +354,8 @@ getTermLine cc = _ -> error "no output for 5 seconds" userName :: TestCC -> IO [Char] -userName (TestCC ChatController {currentUser} _ _ _ _ _) = maybe "no current user" (T.unpack . localDisplayName) <$> readTVarIO currentUser +userName (TestCC ChatController {currentUser} _ _ _ _ _) = + maybe "no current user" (\User {localDisplayName} -> T.unpack localDisplayName) <$> readTVarIO currentUser testChat2 :: HasCallStack => Profile -> Profile -> (HasCallStack => TestCC -> TestCC -> IO ()) -> FilePath -> IO () testChat2 = testChatCfgOpts2 testCfg testOpts @@ -385,7 +422,7 @@ serverCfg = logStatsStartTime = 0, serverStatsLogFile = "tests/smp-server-stats.daily.log", serverStatsBackupFile = Nothing, - smpServerVRange = supportedSMPServerVRange, + smpServerVRange = supportedServerSMPRelayVRange, transportConfig = defaultTransportServerConfig, smpHandshakeTimeout = 1000000, controlPort = Nothing @@ -408,10 +445,11 @@ xftpServerConfig = storeLogFile = Just "tests/tmp/xftp-server-store.log", filesPath = xftpServerFiles, fileSizeQuota = Nothing, - allowedChunkSizes = [kb 128, kb 256, mb 1, mb 4], + allowedChunkSizes = [kb 64, kb 128, kb 256, mb 1, mb 4], allowNewFiles = True, newFileBasicAuth = Nothing, fileExpiration = Just defaultFileExpiration, + fileTimeout = 10000000, inactiveClientExpiration = Just defaultInactiveClientExpiration, caCertificateFile = "tests/fixtures/tls/ca.crt", privateKeyFile = "tests/fixtures/tls/server.key", diff --git a/tests/ChatTests/ChatList.hs b/tests/ChatTests/ChatList.hs index 8492ab0f0d..7f02fafc2c 100644 --- a/tests/ChatTests/ChatList.hs +++ b/tests/ChatTests/ChatList.hs @@ -199,14 +199,14 @@ testPaginationAllChatTypes = ts7 <- iso8601Show <$> getCurrentTime - getChats_ alice "count=10" [("*", "psst"), ("@dan", "hey"), ("#team", ""), (":3", ""), ("<@cath", ""), ("@bob", "hey")] - getChats_ alice "count=3" [("*", "psst"), ("@dan", "hey"), ("#team", "")] + getChats_ alice "count=10" [("*", "psst"), ("@dan", "hey"), ("#team", e2eeInfoNoPQStr), (":3", ""), ("<@cath", ""), ("@bob", "hey")] + getChats_ alice "count=3" [("*", "psst"), ("@dan", "hey"), ("#team", e2eeInfoNoPQStr)] getChats_ alice ("after=" <> ts2 <> " count=2") [(":3", ""), ("<@cath", "")] - getChats_ alice ("before=" <> ts5 <> " count=2") [("#team", ""), (":3", "")] - getChats_ alice ("after=" <> ts3 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", ""), (":3", "")] + getChats_ alice ("before=" <> ts5 <> " count=2") [("#team", e2eeInfoNoPQStr), (":3", "")] + getChats_ alice ("after=" <> ts3 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", e2eeInfoNoPQStr), (":3", "")] getChats_ alice ("before=" <> ts4 <> " count=10") [(":3", ""), ("<@cath", ""), ("@bob", "hey")] - getChats_ alice ("after=" <> ts1 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", ""), (":3", ""), ("<@cath", ""), ("@bob", "hey")] - getChats_ alice ("before=" <> ts7 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", ""), (":3", ""), ("<@cath", ""), ("@bob", "hey")] + getChats_ alice ("after=" <> ts1 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", e2eeInfoNoPQStr), (":3", ""), ("<@cath", ""), ("@bob", "hey")] + getChats_ alice ("before=" <> ts7 <> " count=10") [("*", "psst"), ("@dan", "hey"), ("#team", e2eeInfoNoPQStr), (":3", ""), ("<@cath", ""), ("@bob", "hey")] getChats_ alice ("after=" <> ts7 <> " count=10") [] getChats_ alice ("before=" <> ts1 <> " count=10") [] @@ -218,11 +218,11 @@ testPaginationAllChatTypes = alice ##> "/_settings #1 {\"enableNtfs\":\"all\",\"favorite\":true}" alice <## "ok" - getChats_ alice queryFavorite [("#team", ""), ("@bob", "hey")] + getChats_ alice queryFavorite [("#team", e2eeInfoNoPQStr), ("@bob", "hey")] getChats_ alice ("before=" <> ts4 <> " count=1 " <> queryFavorite) [("@bob", "hey")] - getChats_ alice ("before=" <> ts5 <> " count=1 " <> queryFavorite) [("#team", "")] + getChats_ alice ("before=" <> ts5 <> " count=1 " <> queryFavorite) [("#team", e2eeInfoNoPQStr)] getChats_ alice ("after=" <> ts1 <> " count=1 " <> queryFavorite) [("@bob", "hey")] - getChats_ alice ("after=" <> ts4 <> " count=1 " <> queryFavorite) [("#team", "")] + getChats_ alice ("after=" <> ts4 <> " count=1 " <> queryFavorite) [("#team", e2eeInfoNoPQStr)] let queryUnread = "{\"type\": \"filters\", \"favorite\": false, \"unread\": true}" diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index d17a94dbdf..4e06f68fb6 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -1,5 +1,6 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE PostfixOperators #-} {-# LANGUAGE RankNTypes #-} @@ -9,18 +10,23 @@ import ChatClient import ChatTests.Utils import Control.Concurrent (threadDelay) import Control.Concurrent.Async (concurrently_) -import Control.Monad (forM_) +import Control.Monad (forM_, when) import Data.Aeson (ToJSON) import qualified Data.Aeson as J import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LB +import qualified Data.Text as T +import Simplex.Chat.AppSettings (defaultAppSettings) +import qualified Simplex.Chat.AppSettings as AS import Simplex.Chat.Call import Simplex.Chat.Controller (ChatConfig (..)) import Simplex.Chat.Options (ChatOpts (..)) -import Simplex.Chat.Protocol (supportedChatVRange) +import Simplex.Chat.Protocol (currentChatVersion, pqEncryptionCompressionVersion, supportedChatVRange) import Simplex.Chat.Store (agentStoreFile, chatStoreFile) -import Simplex.Chat.Types (authErrDisableCount, sameVerificationCode, verificationCode) +import Simplex.Chat.Types (VersionRangeChat, authErrDisableCount, sameVerificationCode, verificationCode, VersionChat, pattern VersionChat) import qualified Simplex.Messaging.Crypto as C +import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff, pattern PQSupportOn) +import Simplex.Messaging.Util (safeDecodeUtf8) import Simplex.Messaging.Version import System.Directory (copyFile, doesDirectoryExist, doesFileExist) import System.FilePath ((</>)) @@ -84,8 +90,9 @@ chatDirectTests = do it "disabling chat item expiration doesn't disable it for other users" testDisableCIExpirationOnlyForOneUser it "both users have configured timed messages with contacts, messages expire, restart" testUsersTimedMessages it "user profile privacy: hide profiles and notificaitons" testUserPrivacy - describe "chat item expiration" $ do - it "set chat item TTL" testSetChatItemTTL + describe "settings" $ do + it "set chat item expiration TTL" testSetChatItemTTL + it "save/get app settings" testAppSettings describe "connection switch" $ do it "switch contact to a different queue" testSwitchContact it "stop switching contact to a different queue" testAbortSwitchContact @@ -109,18 +116,25 @@ chatDirectTests = do it "should send delivery receipts depending on configuration" testConfigureDeliveryReceipts describe "negotiate connection peer chat protocol version range" $ do describe "peer version range correctly set for new connection via invitation" $ do - testInvVRange supportedChatVRange supportedChatVRange - testInvVRange supportedChatVRange vr11 - testInvVRange vr11 supportedChatVRange + testInvVRange (supportedChatVRange PQSupportOff) (supportedChatVRange PQSupportOff) + testInvVRange (supportedChatVRange PQSupportOff) vr11 + testInvVRange vr11 (supportedChatVRange PQSupportOff) testInvVRange vr11 vr11 describe "peer version range correctly set for new connection via contact request" $ do - testReqVRange supportedChatVRange supportedChatVRange - testReqVRange supportedChatVRange vr11 - testReqVRange vr11 supportedChatVRange + testReqVRange (supportedChatVRange PQSupportOff) (supportedChatVRange PQSupportOff) + testReqVRange (supportedChatVRange PQSupportOff) vr11 + testReqVRange vr11 (supportedChatVRange PQSupportOff) testReqVRange vr11 vr11 it "update peer version range on received messages" testUpdatePeerChatVRange describe "network statuses" $ do it "should get network statuses" testGetNetworkStatuses + describe "PQ tests" $ do + describe "enable PQ before connection, connect via invitation link" $ pqMatrix2 runTestPQConnectViaLink + describe "enable PQ before connection, connect via contact address" $ pqMatrix2 runTestPQConnectViaAddress + describe "connect via invitation link with PQ encryption enabled" $ pqVersionTestMatrix2 runTestPQVersionsViaLink + describe "connect via contact address with PQ encryption enabled" $ pqVersionTestMatrix2 runTestPQVersionsViaAddress + it "should enable PQ after several messages in connection without PQ" testPQEnableContact + it "should enable PQ, reduce envelope size and enable compression" testPQEnableContactCompression where testInvVRange vr1 vr2 = it (vRangeStr vr1 <> " - " <> vRangeStr vr2) $ testConnInvChatVRange vr1 vr2 testReqVRange vr1 vr2 = it (vRangeStr vr1 <> " - " <> vRangeStr vr2) $ testConnReqChatVRange vr1 vr2 @@ -628,13 +642,13 @@ testDirectLiveMessage = connectUsers alice bob -- non-empty live message is sent instantly alice `send` "/live @bob hello" - bob <# "alice> [LIVE started] use /show [on/off/6] hello" + bob <# "alice> [LIVE started] use /show [on/off/7] hello" alice ##> ("/_update item @2 " <> itemId 1 <> " text hello there") alice <# "@bob [LIVE] hello there" bob <# "alice> [LIVE ended] hello there" -- empty live message is also sent instantly alice `send` "/live @bob" - bob <# "alice> [LIVE started] use /show [on/off/7]" + bob <# "alice> [LIVE started] use /show [on/off/8]" alice ##> ("/_update item @2 " <> itemId 2 <> " text hello 2") alice <# "@bob [LIVE] hello 2" bob <# "alice> [LIVE ended] hello 2" @@ -1138,6 +1152,10 @@ testDatabaseEncryption tmp = do testChatWorking alice bob alice ##> "/_stop" alice <## "chat stopped" + alice ##> "/db test key wrongkey" + alice <## "error opening database after encryption: wrong passphrase or invalid database file" + alice ##> "/db test key mykey" + alice <## "ok" alice ##> "/db key wrongkey nextkey" alice <## "error encrypting database: wrong passphrase or invalid database file" alice ##> "/db key mykey nextkey" @@ -2074,15 +2092,16 @@ testUserPrivacy = alice <##? chatHistory alice ##> "/_get items count=10" alice <##? chatHistory - alice ##> "/_get items before=11 count=10" + alice ##> "/_get items before=13 count=10" alice - <##? [ "bob> Disappearing messages: allowed", + <##? [ ConsoleString ("bob> " <> e2eeInfoNoPQStr), + "bob> Disappearing messages: allowed", "bob> Full deletion: off", "bob> Message reactions: enabled", "bob> Voice messages: enabled", "bob> Audio/video calls: enabled" ] - alice ##> "/_get items after=10 count=10" + alice ##> "/_get items after=12 count=10" alice <##? [ "@bob hello", "bob> hey", @@ -2146,7 +2165,8 @@ testUserPrivacy = alice <## "messages are shown" alice <## "profile is visible" chatHistory = - [ "bob> Disappearing messages: allowed", + [ ConsoleString ("bob> " <> e2eeInfoNoPQStr), + "bob> Disappearing messages: allowed", "bob> Full deletion: off", "bob> Message reactions: enabled", "bob> Voice messages: enabled", @@ -2191,6 +2211,24 @@ testSetChatItemTTL = alice #$> ("/ttl none", id, "ok") alice #$> ("/ttl", id, "old messages are not being deleted") +testAppSettings :: HasCallStack => FilePath -> IO () +testAppSettings tmp = + withNewTestChat tmp "alice" aliceProfile $ \alice -> do + let settings = T.unpack . safeDecodeUtf8 . LB.toStrict $ J.encode defaultAppSettings + settingsApp = T.unpack . safeDecodeUtf8 . LB.toStrict $ J.encode defaultAppSettings {AS.webrtcICEServers = Just ["non-default.value.com"]} + -- app-provided defaults + alice ##> ("/_get app settings " <> settingsApp) + alice <## ("app settings: " <> settingsApp) + -- parser defaults fallback + alice ##> "/_get app settings" + alice <## ("app settings: " <> settings) + -- store + alice ##> ("/_save app settings " <> settingsApp) + alice <## "ok" + -- read back + alice ##> "/_get app settings" + alice <## ("app settings: " <> settingsApp) + testSwitchContact :: HasCallStack => FilePath -> IO () testSwitchContact = testChat2 aliceProfile bobProfile $ @@ -2242,7 +2280,7 @@ testSwitchGroupMember = alice <## "#team: you started changing address for bob" bob <## "#team: alice changed address for you" alice <## "#team: you changed address for bob" - alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "started changing address for bob..."), (1, "you changed address for bob")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "started changing address for bob..."), (1, "you changed address for bob")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "started changing address for you..."), (0, "changed address for you")]) alice #> "#team hey" bob <# "#team alice> hey" @@ -2273,7 +2311,7 @@ testAbortSwitchGroupMember tmp = do bob <## "#team: alice started changing address for you" bob <## "#team: alice changed address for you" alice <## "#team: you changed address for bob" - alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "started changing address for bob..."), (1, "started changing address for bob..."), (1, "you changed address for bob")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "started changing address for bob..."), (1, "started changing address for bob..."), (1, "you changed address for bob")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "started changing address for you..."), (0, "started changing address for you..."), (0, "changed address for you")]) alice #> "#team hey" bob <# "#team alice> hey" @@ -2627,10 +2665,10 @@ testConfigureDeliveryReceipts tmp = cc2 <# (name1 <> "> " <> msg) cc1 <// 50000 -testConnInvChatVRange :: HasCallStack => VersionRange -> VersionRange -> FilePath -> IO () +testConnInvChatVRange :: HasCallStack => VersionRangeChat -> VersionRangeChat -> FilePath -> IO () testConnInvChatVRange ct1VRange ct2VRange tmp = - withNewTestChatCfg tmp testCfg {chatVRange = ct1VRange} "alice" aliceProfile $ \alice -> do - withNewTestChatCfg tmp testCfg {chatVRange = ct2VRange} "bob" bobProfile $ \bob -> do + withNewTestChatCfg tmp testCfg {chatVRange = const ct1VRange} "alice" aliceProfile $ \alice -> do + withNewTestChatCfg tmp testCfg {chatVRange = const ct2VRange} "bob" bobProfile $ \bob -> do connectUsers alice bob alice ##> "/i bob" @@ -2639,10 +2677,10 @@ testConnInvChatVRange ct1VRange ct2VRange tmp = bob ##> "/i alice" contactInfoChatVRange bob ct1VRange -testConnReqChatVRange :: HasCallStack => VersionRange -> VersionRange -> FilePath -> IO () +testConnReqChatVRange :: HasCallStack => VersionRangeChat -> VersionRangeChat -> FilePath -> IO () testConnReqChatVRange ct1VRange ct2VRange tmp = - withNewTestChatCfg tmp testCfg {chatVRange = ct1VRange} "alice" aliceProfile $ \alice -> do - withNewTestChatCfg tmp testCfg {chatVRange = ct2VRange} "bob" bobProfile $ \bob -> do + withNewTestChatCfg tmp testCfg {chatVRange = const ct1VRange} "alice" aliceProfile $ \alice -> do + withNewTestChatCfg tmp testCfg {chatVRange = const ct2VRange} "bob" bobProfile $ \bob -> do alice ##> "/ad" cLink <- getContactLink alice True bob ##> ("/c " <> cLink) @@ -2669,7 +2707,7 @@ testUpdatePeerChatVRange tmp = contactInfoChatVRange alice vr11 bob ##> "/i alice" - contactInfoChatVRange bob supportedChatVRange + contactInfoChatVRange bob (supportedChatVRange PQSupportOff) withTestChat tmp "bob" $ \bob -> do bob <## "1 contacts connected (use /cs for the list)" @@ -2678,10 +2716,10 @@ testUpdatePeerChatVRange tmp = alice <# "bob> hello 1" alice ##> "/i bob" - contactInfoChatVRange alice supportedChatVRange + contactInfoChatVRange alice (supportedChatVRange PQSupportOff) bob ##> "/i alice" - contactInfoChatVRange bob supportedChatVRange + contactInfoChatVRange bob (supportedChatVRange PQSupportOff) withTestChatCfg tmp cfg11 "bob" $ \bob -> do bob <## "1 contacts connected (use /cs for the list)" @@ -2693,9 +2731,9 @@ testUpdatePeerChatVRange tmp = contactInfoChatVRange alice vr11 bob ##> "/i alice" - contactInfoChatVRange bob supportedChatVRange + contactInfoChatVRange bob (supportedChatVRange PQSupportOff) where - cfg11 = testCfg {chatVRange = vr11} :: ChatConfig + cfg11 = testCfg {chatVRange = const vr11} :: ChatConfig testGetNetworkStatuses :: HasCallStack => FilePath -> IO () testGetNetworkStatuses tmp = do @@ -2711,10 +2749,10 @@ testGetNetworkStatuses tmp = do where cfg = testCfg {coreApi = True} -vr11 :: VersionRange -vr11 = mkVersionRange 1 1 +vr11 :: VersionRangeChat +vr11 = mkVersionRange (VersionChat 1) (VersionChat 1) -contactInfoChatVRange :: TestCC -> VersionRange -> IO () +contactInfoChatVRange :: TestCC -> VersionRangeChat -> IO () contactInfoChatVRange cc (VersionRange minVer maxVer) = do cc <## "contact ID: 2" cc <## "receiving messages via: localhost" @@ -2722,3 +2760,253 @@ contactInfoChatVRange cc (VersionRange minVer maxVer) = do cc <## "you've shared main profile with this contact" cc <## "connection not verified, use /code command to see security code" cc <## ("peer chat protocol version range: (" <> show minVer <> ", " <> show maxVer <> ")") + +runTestPQConnectViaLink :: HasCallStack => (TestCC, PQEnabled) -> (TestCC, PQEnabled) -> IO () +runTestPQConnectViaLink (alice, aPQ) (bob, bPQ) = do + when aPQ $ pqOn alice + when bPQ $ pqOn bob + + connectUsers alice bob + + (alice, "hi") `pqSend` bob + (bob, "hey") `pqSend` alice + + alice ##> "/_get chat @2 count=100" + ra <- chat <$> getTermLine alice + ra `shouldContain` [(0, e2eeInfo)] + alice `pqForContact` 2 `shouldReturn` PQEncryption pqEnabled + + bob ##> "/_get chat @2 count=100" + rb <- chat <$> getTermLine bob + rb `shouldContain` [(0, e2eeInfo)] + bob `pqForContact` 2 `shouldReturn` PQEncryption pqEnabled + where + pqEnabled = aPQ && bPQ + pqSend = if pqEnabled then (+#>) else (\#>) + e2eeInfo = if pqEnabled then e2eeInfoPQStr else e2eeInfoNoPQStr + +pqOn :: TestCC -> IO () +pqOn cc = do + cc ##> "/pq on" + cc <## "ok" + +runTestPQConnectViaAddress :: HasCallStack => (TestCC, PQEnabled) -> (TestCC, PQEnabled) -> IO () +runTestPQConnectViaAddress (alice, aPQ) (bob, bPQ) = do + when aPQ $ pqOn alice + when bPQ $ pqOn bob + + alice ##> "/ad" + cLink <- getContactLink alice True + bob ##> ("/c " <> cLink) + alice <#? bob + alice @@@ [("<@bob", "")] + alice ##> "/ac bob" + alice <## "bob (Bob): accepting contact request..." + concurrently_ + (bob <## "alice (Alice): contact is connected") + (alice <## "bob (Bob): contact is connected") + + (alice, "hi") `pqSend` bob + (bob, "hey") `pqSend` alice + + alice ##> "/_get chat @2 count=100" + ra <- chat <$> getTermLine alice + ra `shouldContain` [(0, e2eeInfo)] + alice `pqForContact` 2 `shouldReturn` PQEncryption pqEnabled + + bob ##> "/_get chat @2 count=100" + rb <- chat <$> getTermLine bob + rb `shouldContain` [(0, e2eeInfo)] + bob `pqForContact` 2 `shouldReturn` PQEncryption pqEnabled + where + pqEnabled = aPQ && bPQ + pqSend = if pqEnabled then (+#>) else (\#>) + e2eeInfo = if pqEnabled then e2eeInfoPQStr else e2eeInfoNoPQStr + +runTestPQVersionsViaLink :: HasCallStack => TestCC -> TestCC -> Bool -> VersionChat -> IO () +runTestPQVersionsViaLink alice bob pqExpected vExpected = do + img <- genProfileImg + let profileImage = "data:image/png;base64," <> B.unpack img + alice `send` ("/set profile image " <> profileImage) + _trimmedCmd1 <- getTermLine alice + alice <## "profile image updated" + bob `send` ("/set profile image " <> profileImage) + _trimmedCmd2 <- getTermLine bob + bob <## "profile image updated" + + pqOn alice + pqOn bob + + connectUsers alice bob + + (alice, "hi", vExpected) `pqSend` (bob, vExpected) + (bob, "hey", vExpected) `pqSend` (alice, vExpected) + + alice ##> "/_get chat @2 count=100" + ra <- chat <$> getTermLine alice + ra `shouldContain` [(0, e2eeInfo)] + alice `pqForContact` 2 `shouldReturn` PQEncryption pqExpected + + bob ##> "/_get chat @2 count=100" + rb <- chat <$> getTermLine bob + rb `shouldContain` [(0, e2eeInfo)] + bob `pqForContact` 2 `shouldReturn` PQEncryption pqExpected + where + pqSend = if pqExpected then (+:#>) else (\:#>) + e2eeInfo = if pqExpected then e2eeInfoPQStr else e2eeInfoNoPQStr + +runTestPQVersionsViaAddress :: HasCallStack => TestCC -> TestCC -> Bool -> VersionChat -> IO () +runTestPQVersionsViaAddress alice bob pqExpected vExpected = do + img <- genProfileImg + let profileImage = "data:image/png;base64," <> B.unpack img + alice `send` ("/set profile image " <> profileImage) + _trimmedCmd1 <- getTermLine alice + alice <## "profile image updated" + bob `send` ("/set profile image " <> profileImage) + _trimmedCmd2 <- getTermLine bob + bob <## "profile image updated" + + pqOn alice + pqOn bob + + alice ##> "/ad" + cLink <- getContactLink alice True + bob ##> ("/c " <> cLink) + alice <#? bob + alice @@@ [("<@bob", "")] + alice ##> "/ac bob" + alice <## "bob (Bob): accepting contact request..." + concurrently_ + (bob <## "alice (Alice): contact is connected") + (alice <## "bob (Bob): contact is connected") + + (alice, "hi", vExpected) `pqSend` (bob, vExpected) + (bob, "hey", vExpected) `pqSend` (alice, vExpected) + + alice ##> "/_get chat @2 count=100" + ra <- chat <$> getTermLine alice + ra `shouldContain` [(0, e2eeInfo)] + alice `pqForContact` 2 `shouldReturn` PQEncryption pqExpected + + bob ##> "/_get chat @2 count=100" + rb <- chat <$> getTermLine bob + rb `shouldContain` [(0, e2eeInfo)] + bob `pqForContact` 2 `shouldReturn` PQEncryption pqExpected + where + pqSend = if pqExpected then (+:#>) else (\:#>) + e2eeInfo = if pqExpected then e2eeInfoPQStr else e2eeInfoNoPQStr + +testPQEnableContact :: HasCallStack => FilePath -> IO () +testPQEnableContact = + testChat2 aliceProfile bobProfile $ \alice bob -> do + connectUsers alice bob + (alice, "hi") \#> bob + (bob, "hey") \#> alice + + alice ##> "/_get chat @2 count=100" + ra <- chat <$> getTermLine alice + ra `shouldContain` [(0, e2eeInfoNoPQStr)] + PQEncOff <- alice `pqForContact` 2 + + bob ##> "/_get chat @2 count=100" + rb <- chat <$> getTermLine bob + rb `shouldContain` [(0, e2eeInfoNoPQStr)] + PQEncOff <- bob `pqForContact` 2 + + sendMany PQEncOff alice bob + PQEncOff <- alice `pqForContact` 2 + PQEncOff <- bob `pqForContact` 2 + + -- enabling experimental flags doesn't enable PQ in previously created connection + pqOn alice + sendMany PQEncOff alice bob + PQEncOff <- alice `pqForContact` 2 + PQEncOff <- bob `pqForContact` 2 + + pqOn bob + sendMany PQEncOff alice bob + PQEncOff <- alice `pqForContact` 2 + PQEncOff <- bob `pqForContact` 2 + + -- if only one contact allows PQ, it's not enabled + alice ##> "/pq @bob on" + alice <## "bob: enable quantum resistant end-to-end encryption" + sendMany PQEncOff alice bob + PQEncOff <- alice `pqForContact` 2 + PQEncOff <- bob `pqForContact` 2 + + -- both contacts have to allow PQ to enable it + bob ##> "/pq @alice on" + bob <## "alice: enable quantum resistant end-to-end encryption" + + (alice, "1") \#> bob + (bob, "2") \#> alice + (alice, "3") \#> bob + (bob, "4") \#> alice + (alice, "5") +#> bob + + PQEncOff <- alice `pqForContact` 2 + PQEncOff <- bob `pqForContact` 2 + + (bob, "6") ++#> alice + -- equivalent to: + -- bob `send` "@alice 6" + -- bob <## "alice: quantum resistant end-to-end encryption enabled" + -- bob <# "@alice 6" + -- alice <## "bob: quantum resistant end-to-end encryption enabled" + -- alice <# "bob> 6" + + PQEncOn <- alice `pqForContact` 2 + alice #$> ("/_get chat @2 count=2", chat, [(0, e2eeInfoPQStr), (0, "6")]) + + PQEncOn <- bob `pqForContact` 2 + bob #$> ("/_get chat @2 count=2", chat, [(1, e2eeInfoPQStr), (1, "6")]) + + (alice, "6") +#> bob + (bob, "7") +#> alice + + sendMany PQEncOn alice bob + + PQEncOn <- alice `pqForContact` 2 + PQEncOn <- bob `pqForContact` 2 + pure () + +sendMany :: PQEncryption -> TestCC -> TestCC -> IO () +sendMany pqEnc alice bob = + forM_ [(1 :: Int) .. 10] $ \i -> do + sndRcv pqEnc False (alice, show i) bob + sndRcv pqEnc False (bob, show i) alice + +testPQEnableContactCompression :: HasCallStack => FilePath -> IO () +testPQEnableContactCompression = + testChat2 aliceProfile bobProfile $ \alice bob -> do + connectUsers alice bob + (alice, "hi") \#> bob + (bob, "hey") \#> alice + PQEncOff <- alice `pqForContact` 2 + PQEncOff <- bob `pqForContact` 2 + (alice, "lrg 1", v) \:#> (bob, v) + (bob, "lrg 2", v) \:#> (alice, v) + PQSupportOff <- alice `pqSupportForCt` 2 + alice ##> "/pq @bob on" + alice <## "bob: enable quantum resistant end-to-end encryption" + PQSupportOn <- alice `pqSupportForCt` 2 + (alice, "lrg 3", v) \:#> (bob, v) + (bob, "lrg 4", v) \:#> (alice, v) + PQSupportOff <- bob `pqSupportForCt` 2 + bob ##> "/pq @alice on" + bob <## "alice: enable quantum resistant end-to-end encryption" + PQSupportOn <- bob `pqSupportForCt` 2 + (alice, "lrg 1", v) \:#> (bob, v') + (bob, "lrg 2", v') \:#> (alice, v') + (alice, "lrg 3", v') \:#> (bob, v') + (bob, "lrg 4", v') \:#> (alice, v') + (alice, "lrg 5", v') +:#> (bob, v') + PQEncOff <- alice `pqForContact` 2 + PQEncOff <- bob `pqForContact` 2 + (bob, "lrg 6", v') ++:#> (alice, v') + (alice, "lrg 7", v') +:#> (bob, v') + (bob, "lrg 8", v') +:#> (alice, v') + where + v = currentChatVersion + v' = pqEncryptionCompressionVersion diff --git a/tests/ChatTests/Files.hs b/tests/ChatTests/Files.hs index 1b34f909f1..1e72df9156 100644 --- a/tests/ChatTests/Files.hs +++ b/tests/ChatTests/Files.hs @@ -9,9 +9,11 @@ import ChatClient import ChatTests.Utils import Control.Concurrent (threadDelay) import Control.Concurrent.Async (concurrently_) +import Control.Logger.Simple import qualified Data.Aeson as J import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LB +import Network.HTTP.Types.URI (urlEncode) import Simplex.Chat (roundedFDCount) import Simplex.Chat.Controller (ChatConfig (..)) import Simplex.Chat.Mobile.File @@ -19,7 +21,6 @@ import Simplex.Chat.Options (ChatOpts (..)) import Simplex.FileTransfer.Server.Env (XFTPServerConfig (..)) import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Util (unlessM) import System.Directory (copyFile, createDirectoryIfMissing, doesFileExist, getFileSize) import Test.Hspec hiding (it) @@ -50,6 +51,14 @@ chatFileTests = do it "cancel receiving file, repeat receive" testXFTPCancelRcvRepeat it "should accept file automatically with CLI option" testAutoAcceptFile it "should prohibit file transfers in groups based on preference" testProhibitFiles + describe "file transfer over XFTP without chat items" $ do + it "send and receive small standalone file" testXFTPStandaloneSmall + it "send and receive small standalone file with extra information" testXFTPStandaloneSmallInfo + it "send and receive large standalone file" testXFTPStandaloneLarge + it "send and receive large standalone file with extra information" testXFTPStandaloneLargeInfo + it "send and receive large standalone file using relative paths" testXFTPStandaloneRelativePaths + xit "removes sent file from server" testXFTPStandaloneCancelSnd -- no error shown in tests + it "removes received temporary files" testXFTPStandaloneCancelRcv runTestMessageWithFile :: HasCallStack => FilePath -> IO () runTestMessageWithFile = testChat2 aliceProfile bobProfile $ \alice bob -> withXFTPServer $ do @@ -838,5 +847,206 @@ testProhibitFiles = (bob </) (cath </) -waitFileExists :: HasCallStack => FilePath -> IO () -waitFileExists f = unlessM (doesFileExist f) $ waitFileExists f +testXFTPStandaloneSmall :: HasCallStack => FilePath -> IO () +testXFTPStandaloneSmall = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do + withXFTPServer $ do + logNote "sending" + src ##> "/_upload 1 ./tests/fixtures/logo.jpg" + src <## "started standalone uploading file 1 (logo.jpg)" + -- silent progress events + threadDelay 250000 + src <## "file 1 (logo.jpg) upload complete. download with:" + -- file description fits, enjoy the direct URIs + _uri1 <- getTermLine src + _uri2 <- getTermLine src + uri3 <- getTermLine src + _uri4 <- getTermLine src + + logNote "receiving" + let dstFile = "./tests/tmp/logo.jpg" + dst ##> ("/_download 1 " <> uri3 <> " " <> dstFile) + dst <## "started standalone receiving file 1 (logo.jpg)" + -- silent progress events + threadDelay 250000 + dst <## "completed standalone receiving file 1 (logo.jpg)" + srcBody <- B.readFile "./tests/fixtures/logo.jpg" + B.readFile dstFile `shouldReturn` srcBody + +testXFTPStandaloneSmallInfo :: HasCallStack => FilePath -> IO () +testXFTPStandaloneSmallInfo = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do + withXFTPServer $ do + logNote "sending" + src ##> "/_upload 1 ./tests/fixtures/logo.jpg" + src <## "started standalone uploading file 1 (logo.jpg)" + -- silent progress events + threadDelay 250000 + src <## "file 1 (logo.jpg) upload complete. download with:" + -- file description fits, enjoy the direct URIs + _uri1 <- getTermLine src + _uri2 <- getTermLine src + uri3 <- getTermLine src + _uri4 <- getTermLine src + let uri = uri3 <> "&data=" <> B.unpack (urlEncode False . LB.toStrict . J.encode $ J.object ["secret" J..= J.String "*********"]) + + logNote "info" + dst ##> ("/_download info " <> uri) + dst <## "{\"secret\":\"*********\"}" + + logNote "receiving" + let dstFile = "./tests/tmp/logo.jpg" + dst ##> ("/_download 1 " <> uri <> " " <> dstFile) -- download sucessfully discarded extra info + dst <## "started standalone receiving file 1 (logo.jpg)" + -- silent progress events + threadDelay 250000 + dst <## "completed standalone receiving file 1 (logo.jpg)" + srcBody <- B.readFile "./tests/fixtures/logo.jpg" + B.readFile dstFile `shouldReturn` srcBody + +testXFTPStandaloneLarge :: HasCallStack => FilePath -> IO () +testXFTPStandaloneLarge = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do + withXFTPServer $ do + xftpCLI ["rand", "./tests/tmp/testfile.in", "17mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile.in"] + + logNote "sending" + src ##> "/_upload 1 ./tests/tmp/testfile.in" + src <## "started standalone uploading file 1 (testfile.in)" + -- silent progress events + threadDelay 250000 + src <## "file 1 (testfile.in) uploaded, preparing redirect file 2" + src <## "file 1 (testfile.in) upload complete. download with:" + uri <- getTermLine src + _uri2 <- getTermLine src + _uri3 <- getTermLine src + _uri4 <- getTermLine src + + logNote "receiving" + let dstFile = "./tests/tmp/testfile.out" + dst ##> ("/_download 1 " <> uri <> " " <> dstFile) + dst <## "started standalone receiving file 1 (testfile.out)" + -- silent progress events + threadDelay 250000 + dst <## "completed standalone receiving file 1 (testfile.out)" + srcBody <- B.readFile "./tests/tmp/testfile.in" + B.readFile dstFile `shouldReturn` srcBody + +testXFTPStandaloneLargeInfo :: HasCallStack => FilePath -> IO () +testXFTPStandaloneLargeInfo = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do + withXFTPServer $ do + xftpCLI ["rand", "./tests/tmp/testfile.in", "17mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile.in"] + + logNote "sending" + src ##> "/_upload 1 ./tests/tmp/testfile.in" + src <## "started standalone uploading file 1 (testfile.in)" + + -- silent progress events + threadDelay 250000 + src <## "file 1 (testfile.in) uploaded, preparing redirect file 2" + src <## "file 1 (testfile.in) upload complete. download with:" + uri1 <- getTermLine src + _uri2 <- getTermLine src + _uri3 <- getTermLine src + _uri4 <- getTermLine src + let uri = uri1 <> "&data=" <> B.unpack (urlEncode False . LB.toStrict . J.encode $ J.object ["secret" J..= J.String "*********"]) + + logNote "info" + dst ##> ("/_download info " <> uri) + dst <## "{\"secret\":\"*********\"}" + + logNote "receiving" + let dstFile = "./tests/tmp/testfile.out" + dst ##> ("/_download 1 " <> uri <> " " <> dstFile) + dst <## "started standalone receiving file 1 (testfile.out)" + -- silent progress events + threadDelay 250000 + dst <## "completed standalone receiving file 1 (testfile.out)" + srcBody <- B.readFile "./tests/tmp/testfile.in" + B.readFile dstFile `shouldReturn` srcBody + +testXFTPStandaloneCancelSnd :: HasCallStack => FilePath -> IO () +testXFTPStandaloneCancelSnd = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do + withXFTPServer $ do + xftpCLI ["rand", "./tests/tmp/testfile.in", "17mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile.in"] + + logNote "sending" + src ##> "/_upload 1 ./tests/tmp/testfile.in" + src <## "started standalone uploading file 1 (testfile.in)" + -- silent progress events + threadDelay 250000 + src <## "file 1 (testfile.in) uploaded, preparing redirect file 2" + src <## "file 1 (testfile.in) upload complete. download with:" + uri <- getTermLine src + _uri2 <- getTermLine src + _uri3 <- getTermLine src + _uri4 <- getTermLine src + + logNote "cancelling" + src ##> "/fc 1" + src <## "cancelled sending file 1 (testfile.in)" + threadDelay 1000000 + + logNote "trying to receive cancelled" + dst ##> ("/_download 1 " <> uri <> " " <> "./tests/tmp/should.not.extist") + dst <## "started standalone receiving file 1 (should.not.extist)" + threadDelay 100000 + logWarn "no error?" + dst <## "error receiving file 1 (should.not.extist)" + dst <## "INTERNAL {internalErr = \"XFTP {xftpErr = AUTH}\"}" + +testXFTPStandaloneRelativePaths :: HasCallStack => FilePath -> IO () +testXFTPStandaloneRelativePaths = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do + withXFTPServer $ do + logNote "sending" + src #$> ("/_files_folder ./tests/tmp/src_files", id, "ok") + src #$> ("/_temp_folder ./tests/tmp/src_xftp_temp", id, "ok") + + xftpCLI ["rand", "./tests/tmp/src_files/testfile.in", "17mb"] `shouldReturn` ["File created: " <> "./tests/tmp/src_files/testfile.in"] + + src ##> "/_upload 1 testfile.in" + src <## "started standalone uploading file 1 (testfile.in)" + -- silent progress events + threadDelay 250000 + src <## "file 1 (testfile.in) uploaded, preparing redirect file 2" + src <## "file 1 (testfile.in) upload complete. download with:" + uri <- getTermLine src + _uri2 <- getTermLine src + _uri3 <- getTermLine src + _uri4 <- getTermLine src + + logNote "receiving" + dst #$> ("/_files_folder ./tests/tmp/dst_files", id, "ok") + dst #$> ("/_temp_folder ./tests/tmp/dst_xftp_temp", id, "ok") + dst ##> ("/_download 1 " <> uri <> " testfile.out") + dst <## "started standalone receiving file 1 (testfile.out)" + -- silent progress events + threadDelay 250000 + dst <## "completed standalone receiving file 1 (testfile.out)" + srcBody <- B.readFile "./tests/tmp/src_files/testfile.in" + B.readFile "./tests/tmp/dst_files/testfile.out" `shouldReturn` srcBody + +testXFTPStandaloneCancelRcv :: HasCallStack => FilePath -> IO () +testXFTPStandaloneCancelRcv = testChat2 aliceProfile aliceDesktopProfile $ \src dst -> do + withXFTPServer $ do + xftpCLI ["rand", "./tests/tmp/testfile.in", "17mb"] `shouldReturn` ["File created: " <> "./tests/tmp/testfile.in"] + + logNote "sending" + src ##> "/_upload 1 ./tests/tmp/testfile.in" + src <## "started standalone uploading file 1 (testfile.in)" + -- silent progress events + threadDelay 250000 + src <## "file 1 (testfile.in) uploaded, preparing redirect file 2" + src <## "file 1 (testfile.in) upload complete. download with:" + uri <- getTermLine src + _uri2 <- getTermLine src + _uri3 <- getTermLine src + _uri4 <- getTermLine src + + logNote "receiving" + let dstFile = "./tests/tmp/testfile.out" + dst ##> ("/_download 1 " <> uri <> " " <> dstFile) + dst <## "started standalone receiving file 1 (testfile.out)" + threadDelay 25000 -- give workers some time to avoid internal errors from starting tasks + logNote "cancelling" + dst ##> "/fc 1" + dst <## "cancelled receiving file 1 (testfile.out)" + threadDelay 25000 + doesFileExist dstFile `shouldReturn` False diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs index 3057fa7b70..bf9b445925 100644 --- a/tests/ChatTests/Groups.hs +++ b/tests/ChatTests/Groups.hs @@ -1,4 +1,5 @@ {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE PostfixOperators #-} module ChatTests.Groups where @@ -14,9 +15,9 @@ import qualified Data.Text as T import Simplex.Chat.Controller (ChatConfig (..)) import Simplex.Chat.Protocol (supportedChatVRange) import Simplex.Chat.Store (agentStoreFile, chatStoreFile) -import Simplex.Chat.Types (GroupMemberRole (..)) +import Simplex.Chat.Types (GroupMemberRole (..), VersionRangeChat) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB -import Simplex.Messaging.Version +import Simplex.Messaging.Crypto.Ratchet (pattern PQSupportOff) import System.Directory (copyFile) import System.FilePath ((</>)) import Test.Hspec hiding (it) @@ -148,19 +149,19 @@ chatGroupTests = do it "member was blocked before joining group" testBlockForAllBeforeJoining it "can't repeat block, unblock" testBlockForAllCantRepeat where - _0 = supportedChatVRange -- don't create direct connections + _0 = supportedChatVRange PQSupportOff -- don't create direct connections _1 = groupCreateDirectVRange -- having host configured with older version doesn't have effect in tests -- because host uses current code and sends version in MemberInfo testNoDirect vrMem2 vrMem3 noConns = it ( "host " - <> vRangeStr supportedChatVRange + <> vRangeStr (supportedChatVRange PQSupportOff) <> (", 2nd mem " <> vRangeStr vrMem2) <> (", 3rd mem " <> vRangeStr vrMem3) <> (if noConns then " : 2 <!!> 3" else " : 2 <##> 3") ) - $ testNoGroupDirectConns supportedChatVRange vrMem2 vrMem3 noConns + $ testNoGroupDirectConns (supportedChatVRange PQSupportOff) vrMem2 vrMem3 noConns testGroup :: HasCallStack => FilePath -> IO () testGroup = @@ -336,11 +337,11 @@ testGroupShared alice bob cath checkMessages directConnections = do getReadChats :: HasCallStack => String -> String -> IO () getReadChats msgItem1 msgItem2 = do alice @@@ [("#team", "hey team"), ("@cath", "sent invitation to join group team as admin"), ("@bob", "sent invitation to join group team as admin")] - alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (0, "connected"), (1, "hello"), (0, "hi there"), (0, "hey team")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (0, "connected"), (1, "hello"), (0, "hi there"), (0, "hey team")]) -- "before" and "after" define a chat item id across all chats, -- so we take into account group event items as well as sent group invitations in direct chats alice #$> ("/_get chat #1 after=" <> msgItem1 <> " count=100", chat, [(0, "hi there"), (0, "hey team")]) - alice #$> ("/_get chat #1 before=" <> msgItem2 <> " count=100", chat, [(0, "connected"), (0, "connected"), (1, "hello"), (0, "hi there")]) + alice #$> ("/_get chat #1 before=" <> msgItem2 <> " count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (0, "connected"), (1, "hello"), (0, "hi there")]) alice #$> ("/_get chat #1 count=100 search=team", chat, [(0, "hey team")]) bob @@@ [("@cath", "hey"), ("#team", "hey team"), ("@alice", "received invitation to join group team as admin")] bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "added cath (Catherine)"), (0, "connected"), (0, "hello"), (1, "hi there"), (0, "hey team")]) @@ -499,9 +500,10 @@ testGroup2 = dan <##> cath dan <##> alice -- show last messages - alice ##> "/t #club 8" + alice ##> "/t #club 9" alice -- these strings are expected in any order because of sorting by time and rounding of time for sent - <##? [ "#club bob> connected", + <##? [ ConsoleString ("#club " <> e2eeInfoNoPQStr), + "#club bob> connected", "#club cath> connected", "#club bob> added dan (Daniel)", "#club dan> connected", @@ -1858,7 +1860,7 @@ testGroupLink = bob <## "#team: you joined the group" ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "invited via your group link"), (0, "connected")]) -- contacts connected via group link are not in chat previews alice @@@ [("#team", "connected")] bob @@@ [("#team", "connected")] @@ -2697,7 +2699,7 @@ testGroupLinkNoContact = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) alice @@@ [("#team", "connected")] bob @@@ [("#team", "connected")] @@ -2760,7 +2762,7 @@ testGroupLinkNoContactInviteesWereConnected = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) alice @@@ [("#team", "connected")] bob @@@ [("#team", "connected"), ("@cath", "hey")] @@ -2841,7 +2843,7 @@ testGroupLinkNoContactAllMembersWereConnected = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (1, "Recent history: off"), (0, "invited via your group link"), (0, "connected")]) alice @@@ [("#team", "connected"), ("@bob", "hey"), ("@cath", "hey")] bob @@@ [("#team", "connected"), ("@alice", "hey"), ("@cath", "hey")] @@ -2996,7 +2998,7 @@ testGroupLinkNoContactHostIncognito = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "invited via your group link"), (0, "connected")]) alice @@@ [("#team", "connected")] bob @@@ [("#team", "connected")] @@ -3029,7 +3031,7 @@ testGroupLinkNoContactInviteeIncognito = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "invited via your group link"), (0, "connected")]) alice @@@ [("#team", "connected")] bob @@@ [("#team", "connected")] @@ -3096,7 +3098,7 @@ testGroupLinkNoContactExistingContactMerged = ] threadDelay 100000 - alice #$> ("/_get chat #1 count=100", chat, [(0, "invited via your group link"), (0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "invited via your group link"), (0, "connected")]) alice <##> bob @@ -3579,11 +3581,11 @@ testConfigureGroupDeliveryReceipts tmp = cc3 <# ("#" <> gName <> " " <> name1 <> "> " <> msg) cc1 <// 50000 -testNoGroupDirectConns :: HasCallStack => VersionRange -> VersionRange -> VersionRange -> Bool -> FilePath -> IO () +testNoGroupDirectConns :: HasCallStack => VersionRangeChat -> VersionRangeChat -> VersionRangeChat -> Bool -> FilePath -> IO () testNoGroupDirectConns hostVRange mem2VRange mem3VRange noDirectConns tmp = - withNewTestChatCfg tmp testCfg {chatVRange = hostVRange} "alice" aliceProfile $ \alice -> do - withNewTestChatCfg tmp testCfg {chatVRange = mem2VRange} "bob" bobProfile $ \bob -> do - withNewTestChatCfg tmp testCfg {chatVRange = mem3VRange} "cath" cathProfile $ \cath -> do + withNewTestChatCfg tmp testCfg {chatVRange = const hostVRange} "alice" aliceProfile $ \alice -> do + withNewTestChatCfg tmp testCfg {chatVRange = const mem2VRange} "bob" bobProfile $ \bob -> do + withNewTestChatCfg tmp testCfg {chatVRange = const mem3VRange} "cath" cathProfile $ \cath -> do createGroup3 "team" alice bob cath if noDirectConns then contactsDontExist bob cath @@ -5050,8 +5052,7 @@ testGroupHistoryDeletedMessage = testGroupHistoryDisappearingMessage :: HasCallStack => FilePath -> IO () testGroupHistoryDisappearingMessage = testChat3 aliceProfile bobProfile cathProfile $ - -- \alice bob cath -> do -- revert when test is stable - \a b c -> withTestOutput a $ \alice -> withTestOutput b $ \bob -> withTestOutput c $ \cath -> do + \alice bob cath -> do createGroup2 "team" alice bob threadDelay 1000000 diff --git a/tests/ChatTests/Local.hs b/tests/ChatTests/Local.hs index 6ea41a2387..40ebe51b83 100644 --- a/tests/ChatTests/Local.hs +++ b/tests/ChatTests/Local.hs @@ -151,7 +151,7 @@ testFiles tmp = withNewTestChat tmp "alice" aliceProfile $ \alice -> do alice ##> "/clear *" alice ##> "/fs 1" - alice <## "chat db error: SEChatItemNotFoundByFileId {fileId = 1}" + alice <## "file 1 not found" alice ##> "/tail" doesFileExist stored `shouldReturn` False diff --git a/tests/ChatTests/Profiles.hs b/tests/ChatTests/Profiles.hs index 30c78138ad..7996fde3ad 100644 --- a/tests/ChatTests/Profiles.hs +++ b/tests/ChatTests/Profiles.hs @@ -1509,7 +1509,7 @@ testSetContactPrefs = testChat2 aliceProfile bobProfile $ alice ##> "/_set prefs @2 {}" alice <## "your preferences for bob did not change" (bob </) - let startFeatures = [(0, "Disappearing messages: allowed"), (0, "Full deletion: off"), (0, "Message reactions: enabled"), (0, "Voice messages: off"), (0, "Audio/video calls: enabled")] + let startFeatures = [(0, e2eeInfoNoPQStr), (0, "Disappearing messages: allowed"), (0, "Full deletion: off"), (0, "Message reactions: enabled"), (0, "Voice messages: off"), (0, "Audio/video calls: enabled")] alice #$> ("/_get chat @2 count=100", chat, startFeatures) bob #$> ("/_get chat @2 count=100", chat, startFeatures) let sendVoice = "/_send @2 json {\"filePath\": \"test.txt\", \"msgContent\": {\"type\": \"voice\", \"text\": \"\", \"duration\": 10}}" @@ -1608,13 +1608,13 @@ testUpdateGroupPrefs = testChat2 aliceProfile bobProfile $ \alice bob -> do createGroup2 "team" alice bob - alice #$> ("/_get chat #1 count=100", chat, [(0, "connected")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected")]) threadDelay 500000 bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected")]) alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"on\"}, \"directMessages\": {\"enable\": \"on\"}, \"history\": {\"enable\": \"on\"}}}" alice <## "updated group preferences:" alice <## "Full deletion: on" - alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "Full deletion: on")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Full deletion: on")]) bob <## "alice updated group #team:" bob <## "updated group preferences:" bob <## "Full deletion: on" @@ -1624,7 +1624,7 @@ testUpdateGroupPrefs = alice <## "updated group preferences:" alice <## "Full deletion: off" alice <## "Voice messages: off" - alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off")]) bob <## "alice updated group #team:" bob <## "updated group preferences:" bob <## "Full deletion: off" @@ -1634,7 +1634,7 @@ testUpdateGroupPrefs = alice ##> "/set voice #team on" alice <## "updated group preferences:" alice <## "Voice messages: on" - alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off"), (1, "Voice messages: on")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off"), (1, "Voice messages: on")]) bob <## "alice updated group #team:" bob <## "updated group preferences:" bob <## "Voice messages: on" @@ -1644,14 +1644,14 @@ testUpdateGroupPrefs = alice ##> "/_group_profile #1 {\"displayName\": \"team\", \"fullName\": \"\", \"groupPreferences\": {\"fullDelete\": {\"enable\": \"off\"}, \"voice\": {\"enable\": \"on\"}, \"directMessages\": {\"enable\": \"on\"}, \"history\": {\"enable\": \"on\"}}}" -- no update threadDelay 500000 - alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off"), (1, "Voice messages: on")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off"), (1, "Voice messages: on")]) alice #> "#team hey" bob <# "#team alice> hey" threadDelay 1000000 bob #> "#team hi" alice <# "#team bob> hi" threadDelay 500000 - alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off"), (1, "Voice messages: on"), (1, "hey"), (0, "hi")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Full deletion: on"), (1, "Full deletion: off"), (1, "Voice messages: off"), (1, "Voice messages: on"), (1, "hey"), (0, "hi")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "Full deletion: on"), (0, "Full deletion: off"), (0, "Voice messages: off"), (0, "Voice messages: on"), (0, "hey"), (1, "hi")]) testAllowFullDeletionContact :: HasCallStack => FilePath -> IO () @@ -1677,7 +1677,7 @@ testAllowFullDeletionGroup = testChat2 aliceProfile bobProfile $ \alice bob -> do createGroup2 "team" alice bob - threadDelay 1000000 + threadDelay 1500000 alice #> "#team hi" bob <# "#team alice> hi" threadDelay 1000000 @@ -1691,11 +1691,11 @@ testAllowFullDeletionGroup = bob <## "alice updated group #team:" bob <## "updated group preferences:" bob <## "Full deletion: on" - alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "hi"), (0, "hey"), (1, "Full deletion: on")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "hi"), (0, "hey"), (1, "Full deletion: on")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "hi"), (1, "hey"), (0, "Full deletion: on")]) bob #$> ("/_delete item #1 " <> msgItemId <> " broadcast", id, "message deleted") alice <# "#team bob> [deleted] hey" - alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "hi"), (1, "Full deletion: on")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "hi"), (1, "Full deletion: on")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "hi"), (0, "Full deletion: on")]) testProhibitDirectMessages :: HasCallStack => FilePath -> IO () @@ -1817,12 +1817,12 @@ testEnableTimedMessagesGroup = alice #> "#team hi" bob <# "#team alice> hi" threadDelay 500000 - alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "Disappearing messages: on (1 sec)"), (1, "hi")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Disappearing messages: on (1 sec)"), (1, "hi")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "Disappearing messages: on (1 sec)"), (0, "hi")]) threadDelay 1000000 alice <## "timed message deleted: hi" bob <## "timed message deleted: hi" - alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "Disappearing messages: on (1 sec)")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Disappearing messages: on (1 sec)")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "Disappearing messages: on (1 sec)")]) -- turn off, messages are not disappearing alice ##> "/set disappear #team off" @@ -1835,7 +1835,7 @@ testEnableTimedMessagesGroup = alice #> "#team hey" bob <# "#team alice> hey" threadDelay 1500000 - alice #$> ("/_get chat #1 count=100", chat, [(0, "connected"), (1, "Disappearing messages: on (1 sec)"), (1, "Disappearing messages: off"), (1, "hey")]) + alice #$> ("/_get chat #1 count=100", chat, [(1, e2eeInfoNoPQStr), (0, "connected"), (1, "Disappearing messages: on (1 sec)"), (1, "Disappearing messages: off"), (1, "hey")]) bob #$> ("/_get chat #1 count=100", chat, groupFeatures <> [(0, "connected"), (0, "Disappearing messages: on (1 sec)"), (0, "Disappearing messages: off"), (0, "hey")]) -- test api alice ##> "/set disappear #team on 30s" diff --git a/tests/ChatTests/Utils.hs b/tests/ChatTests/Utils.hs index 9ce84be18e..3b0748e7d0 100644 --- a/tests/ChatTests/Utils.hs +++ b/tests/ChatTests/Utils.hs @@ -2,6 +2,7 @@ {-# LANGUAGE LambdaCase #-} {-# LANGUAGE NamedFieldPuns #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} {-# LANGUAGE RankNTypes #-} module ChatTests.Utils where @@ -12,6 +13,8 @@ import Control.Concurrent.Async (concurrently_) import Control.Concurrent.STM import Control.Monad (unless, when) import Control.Monad.Except (runExceptT) +import Data.ByteString (ByteString) +import qualified Data.ByteString.Base64 as B64 import qualified Data.ByteString.Char8 as B import Data.Char (isDigit) import Data.List (isPrefixOf, isSuffixOf) @@ -20,7 +23,9 @@ import Data.String import qualified Data.Text as T import Database.SQLite.Simple (Only (..)) import Simplex.Chat.Controller (ChatConfig (..), ChatController (..)) +import Simplex.Chat.Messages.CIContent (e2eInfoNoPQText, e2eInfoPQText) import Simplex.Chat.Protocol +import Simplex.Chat.Store.Direct (getContact) import Simplex.Chat.Store.NoteFolders (createNoteFolder) import Simplex.Chat.Store.Profiles (getUserContactProfiles) import Simplex.Chat.Types @@ -28,6 +33,8 @@ import Simplex.Chat.Types.Preferences import Simplex.FileTransfer.Client.Main (xftpClientCLI) import Simplex.Messaging.Agent.Store.SQLite (maybeFirstRow, withTransaction) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB +import qualified Simplex.Messaging.Crypto as C +import Simplex.Messaging.Crypto.Ratchet (PQEncryption (..), PQSupport, pattern PQEncOff, pattern PQEncOn, pattern PQSupportOff) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Version import System.Directory (doesFileExist) @@ -76,15 +83,18 @@ ifCI xrun run d t = do ci <- runIO $ lookupEnv "CI" (if ci == Just "true" then xrun else run) d t +skip :: String -> SpecWith a -> SpecWith a +skip = before_ . pendingWith + versionTestMatrix2 :: (HasCallStack => TestCC -> TestCC -> IO ()) -> SpecWith FilePath versionTestMatrix2 runTest = do it "current" $ testChat2 aliceProfile bobProfile runTest it "prev" $ testChatCfg2 testCfgVPrev aliceProfile bobProfile runTest it "prev to curr" $ runTestCfg2 testCfg testCfgVPrev runTest it "curr to prev" $ runTestCfg2 testCfgVPrev testCfg runTest - it "v1" $ testChatCfg2 testCfgV1 aliceProfile bobProfile runTest - it "v1 to v2" $ runTestCfg2 testCfg testCfgV1 runTest - it "v2 to v1" $ runTestCfg2 testCfgV1 testCfg runTest + it "old (1st supported)" $ testChatCfg2 testCfgV1 aliceProfile bobProfile runTest + it "old to curr" $ runTestCfg2 testCfg testCfgV1 runTest + it "curr to old" $ runTestCfg2 testCfgV1 testCfg runTest versionTestMatrix3 :: (HasCallStack => TestCC -> TestCC -> TestCC -> IO ()) -> SpecWith FilePath versionTestMatrix3 runTest = do @@ -108,6 +118,34 @@ runTestCfg3 aliceCfg bobCfg cathCfg runTest tmp = withNewTestChatCfg tmp cathCfg "cath" cathProfile $ \cath -> runTest alice bob cath +type PQEnabled = Bool + +pqMatrix2 :: (HasCallStack => (TestCC, PQEnabled) -> (TestCC, PQEnabled) -> IO ()) -> SpecWith FilePath +pqMatrix2 runTest = do + it "PQ: off, off" $ test False False + it "PQ: on, off" $ test False True + it "PQ: off, on" $ test True False + it "PQ: on, on" $ test True True + where + test aPQ bPQ = testChat2 aliceProfile bobProfile $ \a b -> runTest (a, aPQ) (b, bPQ) + +pqVersionTestMatrix2 :: (HasCallStack => TestCC -> TestCC -> Bool -> VersionChat -> IO ()) -> SpecWith FilePath +pqVersionTestMatrix2 runTest = do + it "current" $ testChat2 aliceProfile bobProfile (runTest' True pqEncryptionCompressionVersion) + it "prev" $ testChatCfg2 testCfgVPrev aliceProfile bobProfile (runTest' False (VersionChat 6)) + it "prev to curr" $ runTestCfg2 testCfg testCfgVPrev (runTest' False (VersionChat 6)) + it "curr to prev" $ runTestCfg2 testCfgVPrev testCfg (runTest' False (VersionChat 6)) + it "old (1st supported)" $ testChatCfg2 testCfgV1 aliceProfile bobProfile (runTest' False (VersionChat 1)) + it "old to curr" $ runTestCfg2 testCfg testCfgV1 (runTest' False (VersionChat 1)) + it "curr to old" $ runTestCfg2 testCfgV1 testCfg (runTest' False (VersionChat 1)) + it "next" $ testChatCfg2 testCfgVNext aliceProfile bobProfile (runTest' True pqEncryptionCompressionVersion) + it "next to curr" $ runTestCfg2 testCfg testCfgVNext (runTest' True pqEncryptionCompressionVersion) + it "curr to next" $ runTestCfg2 testCfgVNext testCfg (runTest' True pqEncryptionCompressionVersion) + it "next to prev" $ runTestCfg2 testCfgVPrev testCfgVNext (runTest' False (VersionChat 6)) + it "prev to next" $ runTestCfg2 testCfgVNext testCfgVPrev (runTest' False (VersionChat 6)) + where + runTest' pqExpected v a b = runTest a b pqExpected v + withTestChatGroup3Connected :: HasCallStack => FilePath -> String -> (HasCallStack => TestCC -> IO a) -> IO a withTestChatGroup3Connected tmp dbPrefix action = do withTestChat tmp dbPrefix $ \cc -> do @@ -166,6 +204,65 @@ cc #$> (cmd, f, res) = do cc ##> cmd (f <$> getTermLine cc) `shouldReturn` res +-- / PQ combinators + +(\#>) :: HasCallStack => (TestCC, String) -> TestCC -> IO () +(\#>) = sndRcv PQEncOff False + +(+#>) :: HasCallStack => (TestCC, String) -> TestCC -> IO () +(+#>) = sndRcv PQEncOn False + +(++#>) :: HasCallStack => (TestCC, String) -> TestCC -> IO () +(++#>) = sndRcv PQEncOn True + +sndRcv :: HasCallStack => PQEncryption -> Bool -> (TestCC, String) -> TestCC -> IO () +sndRcv pqEnc enabled (cc1, msg) cc2 = do + name1 <- userName cc1 + name2 <- userName cc2 + let cmd = "@" <> name2 <> " " <> msg + cc1 `send` cmd + when enabled $ cc1 <## (name2 <> ": quantum resistant end-to-end encryption enabled") + cc1 <# cmd + cc1 `pqSndForContact` 2 `shouldReturn` pqEnc + when enabled $ cc2 <## (name1 <> ": quantum resistant end-to-end encryption enabled") + cc2 <# (name1 <> "> " <> msg) + cc2 `pqRcvForContact` 2 `shouldReturn` pqEnc + +(\:#>) :: HasCallStack => (TestCC, String, VersionChat) -> (TestCC, VersionChat) -> IO () +(\:#>) = sndRcvImg PQEncOff False + +(+:#>) :: HasCallStack => (TestCC, String, VersionChat) -> (TestCC, VersionChat) -> IO () +(+:#>) = sndRcvImg PQEncOn False + +(++:#>) :: HasCallStack => (TestCC, String, VersionChat) -> (TestCC, VersionChat) -> IO () +(++:#>) = sndRcvImg PQEncOn True + +sndRcvImg :: HasCallStack => PQEncryption -> Bool -> (TestCC, String, VersionChat) -> (TestCC, VersionChat) -> IO () +sndRcvImg pqEnc enabled (cc1, msg, v1) (cc2, v2) = do + name1 <- userName cc1 + name2 <- userName cc2 + g <- C.newRandom + img <- atomically $ B64.encode <$> C.randomBytes lrgLen g + cc1 `send` ("/_send @2 json {\"msgContent\":{\"type\":\"image\",\"text\":\"" <> msg <> "\",\"image\":\"" <> B.unpack img <> "\"}}") + cc1 .<## "}}" + cc1 <### ([ConsoleString (name2 <> ": quantum resistant end-to-end encryption enabled") | enabled] <> [WithTime ("@" <> name2 <> " " <> msg)]) + cc1 `pqSndForContact` 2 `shouldReturn` pqEnc + cc1 `pqVerForContact` 2 `shouldReturn` v1 + cc2 <### ([ConsoleString (name1 <> ": quantum resistant end-to-end encryption enabled") | enabled] <> [WithTime (name1 <> "> " <> msg)]) + cc2 `pqRcvForContact` 2 `shouldReturn` pqEnc + cc2 `pqVerForContact` 2 `shouldReturn` v2 + where + lrgLen = maxEncodedMsgLength * 3 `div` 4 - 110 -- 98 is ~ max size for binary image preview given the rest of the message + +genProfileImg :: IO ByteString +genProfileImg = do + g <- C.newRandom + atomically $ B64.encode <$> C.randomBytes lrgLen g + where + lrgLen = maxEncodedInfoLength * 3 `div` 4 - 420 + +-- PQ combinators / + chat :: String -> [(Int, String)] chat = map (\(a, _, _) -> a) . chat'' @@ -189,13 +286,20 @@ chatFeaturesF = map (\(a, _, c) -> (a, c)) chatFeatures'' chatFeatures'' :: [((Int, String), Maybe (Int, String), Maybe String)] chatFeatures'' = - [ ((0, "Disappearing messages: allowed"), Nothing, Nothing), + [ ((0, e2eeInfoNoPQStr), Nothing, Nothing), + ((0, "Disappearing messages: allowed"), Nothing, Nothing), ((0, "Full deletion: off"), Nothing, Nothing), ((0, "Message reactions: enabled"), Nothing, Nothing), ((0, "Voice messages: enabled"), Nothing, Nothing), ((0, "Audio/video calls: enabled"), Nothing, Nothing) ] +e2eeInfoNoPQStr :: String +e2eeInfoNoPQStr = T.unpack e2eInfoNoPQText + +e2eeInfoPQStr :: String +e2eeInfoPQStr = T.unpack e2eInfoPQText + lastChatFeature :: String lastChatFeature = snd $ last chatFeatures @@ -204,7 +308,8 @@ groupFeatures = map (\(a, _, _) -> a) groupFeatures'' groupFeatures'' :: [((Int, String), Maybe (Int, String), Maybe String)] groupFeatures'' = - [ ((0, "Disappearing messages: off"), Nothing, Nothing), + [ ((0, e2eeInfoNoPQStr), Nothing, Nothing), + ((0, "Disappearing messages: off"), Nothing, Nothing), ((0, "Direct messages: on"), Nothing, Nothing), ((0, "Full deletion: off"), Nothing, Nothing), ((0, "Message reactions: on"), Nothing, Nothing), @@ -465,6 +570,34 @@ getProfilePictureByName cc displayName = maybeFirstRow fromOnly $ DB.query db "SELECT image FROM contact_profiles WHERE display_name = ? LIMIT 1" (Only displayName) +pqSndForContact :: TestCC -> ContactId -> IO PQEncryption +pqSndForContact = pqForContact_ pqSndEnabled PQEncOff + +pqRcvForContact :: TestCC -> ContactId -> IO PQEncryption +pqRcvForContact = pqForContact_ pqRcvEnabled PQEncOff + +pqForContact :: TestCC -> ContactId -> IO PQEncryption +pqForContact = pqForContact_ (Just . connPQEnabled) (error "impossible") + +pqSupportForCt :: TestCC -> ContactId -> IO PQSupport +pqSupportForCt = pqForContact_ (\Connection {pqSupport} -> Just pqSupport) PQSupportOff + +pqVerForContact :: TestCC -> ContactId -> IO VersionChat +pqVerForContact = pqForContact_ (Just . connChatVersion) (error "impossible") + +pqForContact_ :: (Connection -> Maybe a) -> a -> TestCC -> ContactId -> IO a +pqForContact_ pqSel def cc contactId = (fromMaybe def . pqSel) <$> getCtConn cc contactId + +getCtConn :: TestCC -> ContactId -> IO Connection +getCtConn cc contactId = getTestCCContact cc contactId >>= maybe (fail "no connection") pure . contactConn + +getTestCCContact :: TestCC -> ContactId -> IO Contact +getTestCCContact cc contactId = do + let TestCC {chatController = ChatController {config = ChatConfig {chatVRange = vr}}} = cc + withCCTransaction cc $ \db -> + withCCUser cc $ \user -> + runExceptT (getContact db vr user contactId) >>= either (fail . show) pure + lastItemId :: HasCallStack => TestCC -> IO String lastItemId cc = do cc ##> "/last_item_id" @@ -573,9 +706,9 @@ checkActionDeletesFile file action = do currentChatVRangeInfo :: String currentChatVRangeInfo = - "peer chat protocol version range: " <> vRangeStr supportedChatVRange + "peer chat protocol version range: " <> vRangeStr (supportedChatVRange PQSupportOff) -vRangeStr :: VersionRange -> String +vRangeStr :: VersionRange v -> String vRangeStr (VersionRange minVer maxVer) = "(" <> show minVer <> ", " <> show maxVer <> ")" linkAnotherSchema :: String -> String diff --git a/tests/MarkdownTests.hs b/tests/MarkdownTests.hs index 1cd2aa2c47..d2d15dc166 100644 --- a/tests/MarkdownTests.hs +++ b/tests/MarkdownTests.hs @@ -153,13 +153,13 @@ textWithUri = describe "text with Uri" do parseMarkdown "_https://simplex.chat" `shouldBe` "_https://simplex.chat" parseMarkdown "this is _https://simplex.chat" `shouldBe` "this is _https://simplex.chat" it "SimpleX links" do - let inv = "/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D" + let inv = "/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D" parseMarkdown ("https://simplex.chat" <> inv) `shouldBe` simplexLink XLInvitation ("simplex:" <> inv) ["smp.simplex.im"] ("https://simplex.chat" <> inv) parseMarkdown ("simplex:" <> inv) `shouldBe` simplexLink XLInvitation ("simplex:" <> inv) ["smp.simplex.im"] ("simplex:" <> inv) parseMarkdown ("https://example.com" <> inv) `shouldBe` simplexLink XLInvitation ("simplex:" <> inv) ["smp.simplex.im"] ("https://example.com" <> inv) - let ct = "/contact#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D" + let ct = "/contact#/?v=2&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D" parseMarkdown ("https://simplex.chat" <> ct) `shouldBe` simplexLink XLContact ("simplex:" <> ct) ["smp.simplex.im"] ("https://simplex.chat" <> ct) - let gr = "/contact#/?v=1-2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FWHV0YU1sYlU7NqiEHkHDB6gxO1ofTync%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAWbebOqVYuBXaiqHcXYjEHCpYi6VzDlu6CVaijDTmsQU%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22mL-7Divb94GGmGmRBef5Dg%3D%3D%22%7D" + let gr = "/contact#/?v=2&smp=smp%3A%2F%2Fu2dS9sG8nMNURyZwqASV4yROM28Er0luVTx5X1CsMrU%3D%40smp4.simplex.im%2FWHV0YU1sYlU7NqiEHkHDB6gxO1ofTync%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAWbebOqVYuBXaiqHcXYjEHCpYi6VzDlu6CVaijDTmsQU%253D%26srv%3Do5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22mL-7Divb94GGmGmRBef5Dg%3D%3D%22%7D" parseMarkdown ("https://simplex.chat" <> gr) `shouldBe` simplexLink XLGroup ("simplex:" <> gr) ["smp4.simplex.im", "o5vmywmrnaxalvz6wi3zicyftgio6psuvyniis6gco6bp6ekl4cqj4id.onion"] ("https://simplex.chat" <> gr) email :: Text -> Markdown diff --git a/tests/MessageBatching.hs b/tests/MessageBatching.hs index 1a9d968718..54a0ae4f1c 100644 --- a/tests/MessageBatching.hs +++ b/tests/MessageBatching.hs @@ -17,7 +17,7 @@ import Data.Text.Encoding (encodeUtf8) import Simplex.Chat.Messages.Batch import Simplex.Chat.Controller (ChatError (..), ChatErrorType (..)) import Simplex.Chat.Messages (SndMessage (..)) -import Simplex.Chat.Protocol (SharedMsgId (..), maxChatMsgSize) +import Simplex.Chat.Protocol (SharedMsgId (..), maxEncodedMsgLength) import Test.Hspec batchingTests :: Spec @@ -99,7 +99,7 @@ testImageFitsSingleBatch = do msg s = SndMessage {msgId = 0, sharedMsgId = SharedMsgId "", msgBody = s} batched = "[" <> xMsgNewStr <> "," <> descrStr <> "]" - runBatcherTest' maxChatMsgSize [msg xMsgNewStr, msg descrStr] [] [batched] + runBatcherTest' maxEncodedMsgLength [msg xMsgNewStr, msg descrStr] [] [batched] runBatcherTest :: Int -> [SndMessage] -> [ChatError] -> [ByteString] -> Spec runBatcherTest maxLen msgs expectedErrors expectedBatches = diff --git a/tests/ProtocolTests.hs b/tests/ProtocolTests.hs index 783f7fb344..082af825e5 100644 --- a/tests/ProtocolTests.hs +++ b/tests/ProtocolTests.hs @@ -2,6 +2,7 @@ {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE OverloadedLists #-} {-# LANGUAGE OverloadedStrings #-} +{-# LANGUAGE PatternSynonyms #-} module ProtocolTests where @@ -15,6 +16,7 @@ import Simplex.Messaging.Agent.Protocol import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.Ratchet import Simplex.Messaging.Protocol (supportedSMPClientVRange) +import Simplex.Messaging.ServiceScheme import Simplex.Messaging.Version import Test.Hspec @@ -37,8 +39,8 @@ queue = connReqData :: ConnReqUriData connReqData = ConnReqUriData - { crScheme = CRSSimplex, - crAgentVRange = mkVersionRange 1 1, + { crScheme = SSSimplex, + crAgentVRange = mkVersionRange (VersionSMPA 1) (VersionSMPA 1), crSmpQueues = [queue], crClientData = Nothing } @@ -46,8 +48,8 @@ connReqData = testDhPubKey :: C.PublicKeyX448 testDhPubKey = "MEIwBQYDK2VvAzkAmKuSYeQ/m0SixPDS8Wq8VBaTS1cW+Lp0n0h4Diu+kUpR+qXx4SDJ32YGEFoGFGSbGPry5Ychr6U=" -testE2ERatchetParams :: E2ERatchetParamsUri 'C.X448 -testE2ERatchetParams = E2ERatchetParamsUri supportedE2EEncryptVRange testDhPubKey testDhPubKey +testE2ERatchetParams :: RcvE2ERatchetParamsUri 'C.X448 +testE2ERatchetParams = E2ERatchetParamsUri (supportedE2EEncryptVRange PQSupportOn) testDhPubKey testDhPubKey Nothing testConnReq :: ConnectionRequestUri 'CMInvitation testConnReq = CRInvitationUri connReqData testE2ERatchetParams @@ -70,12 +72,12 @@ s ==## msg = do (##==) :: MsgEncodingI e => ByteString -> ChatMessage e -> Expectation s ##== msg = do - let r = encodeChatMessage msg + let r = encodeChatMessage maxEncodedMsgLength msg case r of ECMEncoded encodedBody -> J.eitherDecodeStrict' encodedBody `shouldBe` (J.eitherDecodeStrict' s :: Either String J.Value) - ECMLarge -> expectationFailure $ "large message" + ECMLarge -> expectationFailure "large message" (##==##) :: MsgEncodingI e => ByteString -> ChatMessage e -> Expectation s ##==## msg = do @@ -130,7 +132,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do ##==## ChatMessage chatInitialVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing))) it "x.msg.new chat message with chat version range" $ "{\"v\":\"1-7\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" - ##==## ChatMessage supportedChatVRange (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing))) + ##==## ChatMessage (supportedChatVRange PQSupportOff) (Just $ SharedMsgId "\1\2\3\4") (XMsgNew (MCSimple (extMsgContent (MCText "hello") Nothing))) it "x.msg.new quote" $ "{\"v\":\"1\",\"msgId\":\"AQIDBA==\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello to you too\",\"type\":\"text\"},\"quote\":{\"content\":{\"text\":\"hello there!\",\"type\":\"text\"},\"msgRef\":{\"msgId\":\"BQYHCA==\",\"sent\":true,\"sentAt\":\"1970-01-01T00:00:01.000000001Z\"}}}}" ##==## ChatMessage @@ -191,7 +193,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.msg.deleted\",\"params\":{}}" #==# XMsgDeleted it "x.file" $ - "{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" + "{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" #==# XFile FileInvitation {fileName = "photo.jpg", fileSize = 12345, fileDigest = Nothing, fileConnReq = Just testConnReq, fileInline = Nothing, fileDescr = Nothing} it "x.file without file invitation" $ "{\"v\":\"1\",\"event\":\"x.file\",\"params\":{\"file\":{\"fileSize\":12345,\"fileName\":\"photo.jpg\"}}}" @@ -200,7 +202,7 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.file.acpt\",\"params\":{\"fileName\":\"photo.jpg\"}}" #==# XFileAcpt "photo.jpg" it "x.file.acpt.inv" $ - "{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\",\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" + "{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\",\"fileConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" #==# XFileAcptInv (SharedMsgId "\1\2\3\4") (Just testConnReq) "photo.jpg" it "x.file.acpt.inv" $ "{\"v\":\"1\",\"event\":\"x.file.acpt.inv\",\"params\":{\"msgId\":\"AQIDBA==\",\"fileName\":\"photo.jpg\"}}" @@ -227,11 +229,11 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.contact\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"},\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" ==# XContact testProfile Nothing it "x.grp.inv" $ - "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}" - #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, groupLinkId = Nothing} + "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}}}}" + #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, groupLinkId = Nothing, groupSize = Nothing} it "x.grp.inv with group link id" $ - "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}" - #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, groupLinkId = Just $ GroupLinkId "\1\2\3\4"} + "{\"v\":\"1\",\"event\":\"x.grp.inv\",\"params\":{\"groupInvitation\":{\"connRequest\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"invitedMember\":{\"memberRole\":\"member\",\"memberId\":\"BQYHCA==\"},\"groupProfile\":{\"fullName\":\"Team\",\"displayName\":\"team\",\"groupPreferences\":{\"reactions\":{\"enable\":\"on\"},\"voice\":{\"enable\":\"on\"}}},\"fromMember\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\"}, \"groupLinkId\":\"AQIDBA==\"}}}" + #==# XGrpInv GroupInvitation {fromMember = MemberIdRole (MemberId "\1\2\3\4") GRAdmin, invitedMember = MemberIdRole (MemberId "\5\6\7\8") GRMember, connRequest = testConnReq, groupProfile = testGroupProfile, groupLinkId = Just $ GroupLinkId "\1\2\3\4", groupSize = Nothing} it "x.grp.acpt without incognito profile" $ "{\"v\":\"1\",\"event\":\"x.grp.acpt\",\"params\":{\"memberId\":\"AQIDBA==\"}}" #==# XGrpAcpt (MemberId "\1\2\3\4") @@ -240,28 +242,28 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do #==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} it "x.grp.mem.new with member chat version range" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.new\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-7\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" - #==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} + #==# XGrpMemNew MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange $ supportedChatVRange PQSupportOff, profile = testProfile} it "x.grp.mem.intro" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} Nothing it "x.grp.mem.intro with member chat version range" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-7\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" - #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} Nothing + #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange $ supportedChatVRange PQSupportOff, profile = testProfile} Nothing it "x.grp.mem.intro with member restrictions" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.intro\",\"params\":{\"memberRestrictions\":{\"restriction\":\"blocked\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemIntro MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} (Just MemberRestrictions {restriction = MRSBlocked}) it "x.grp.mem.inv" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" #==# XGrpMemInv (MemberId "\1\2\3\4") IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq} it "x.grp.mem.inv w/t directConnReq" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.inv\",\"params\":{\"memberId\":\"AQIDBA==\",\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}}" #==# XGrpMemInv (MemberId "\1\2\3\4") IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing} it "x.grp.mem.fwd" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"directConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\",\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Nothing, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Just testConnReq} it "x.grp.mem.fwd with member chat version range and w/t directConnReq" $ - "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-7\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" - #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange supportedChatVRange, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing} + "{\"v\":\"1\",\"event\":\"x.grp.mem.fwd\",\"params\":{\"memberIntro\":{\"groupConnReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"},\"memberInfo\":{\"memberRole\":\"admin\",\"memberId\":\"AQIDBA==\",\"v\":\"1-7\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}}" + #==# XGrpMemFwd MemberInfo {memberId = MemberId "\1\2\3\4", memberRole = GRAdmin, v = Just $ ChatVersionRange $ supportedChatVRange PQSupportOff, profile = testProfile} IntroInvitation {groupConnReq = testConnReq, directConnReq = Nothing} it "x.grp.mem.info" $ "{\"v\":\"1\",\"event\":\"x.grp.mem.info\",\"params\":{\"memberId\":\"AQIDBA==\",\"profile\":{\"fullName\":\"Alice\",\"displayName\":\"alice\",\"image\":\"data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAgAAAAIAQMAAAD+wSzIAAAABlBMVEX///+/v7+jQ3Y5AAAADklEQVQI12P4AIX8EAgALgAD/aNpbtEAAAAASUVORK5CYII=\",\"preferences\":{\"reactions\":{\"allow\":\"yes\"},\"voice\":{\"allow\":\"yes\"}}}}}" #==# XGrpMemInfo (MemberId "\1\2\3\4") testProfile @@ -281,10 +283,10 @@ decodeChatMessageTest = describe "Chat message encoding/decoding" $ do "{\"v\":\"1\",\"event\":\"x.grp.del\",\"params\":{}}" ==# XGrpDel it "x.grp.direct.inv" $ - "{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\", \"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" + "{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\", \"content\":{\"text\":\"hello\",\"type\":\"text\"}}}" #==# XGrpDirectInv testConnReq (Just $ MCText "hello") it "x.grp.direct.inv without content" $ - "{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D1-2%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" + "{\"v\":\"1\",\"event\":\"x.grp.direct.inv\",\"params\":{\"connReq\":\"simplex:/invitation#/?v=1&smp=smp%3A%2F%2F1234-w%3D%3D%40smp.simplex.im%3A5223%2F3456-w%3D%3D%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAjiswwI3O_NlS8Fk3HJUW870EY2bAwmttMBsvRB9eV3o%253D&e2e=v%3D2-3%26x3dh%3DMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D%2CMEIwBQYDK2VvAzkAmKuSYeQ_m0SixPDS8Wq8VBaTS1cW-Lp0n0h4Diu-kUpR-qXx4SDJ32YGEFoGFGSbGPry5Ychr6U%3D\"}}" #==# XGrpDirectInv testConnReq Nothing -- it "x.grp.msg.forward" -- $ "{\"v\":\"1\",\"event\":\"x.grp.msg.forward\",\"params\":{\"msgForward\":{\"memberId\":\"AQIDBA==\",\"msg\":\"{\"v\":\"1\",\"event\":\"x.msg.new\",\"params\":{\"content\":{\"text\":\"hello\",\"type\":\"text\"}}}\",\"msgTs\":\"1970-01-01T00:00:01.000000001Z\"}}}" diff --git a/website/langs/ar.json b/website/langs/ar.json index 5986d59e89..3b74ac2f3d 100644 --- a/website/langs/ar.json +++ b/website/langs/ar.json @@ -20,10 +20,10 @@ "hero-overlay-1-textlink": "لماذا تعتبر معرّفات المستخدم ضارة بالخصوصية؟", "hero-overlay-2-textlink": "كيف يعمل SimpleX؟", "hero-overlay-2-title": "لماذا تعتبر معرّفات المستخدم ضارة بالخصوصية؟", - "feature-2-title": "تَعْمِيَة<br>الصور والفيديوهات والملفات بين الطرفين", - "feature-3-title": "المجموعات اللامركزية — المستخدمون فقط يعرفون بوجودها", + "feature-2-title": "تعمية<br>الصور والفيديوهات والملفات بين الطرفين", + "feature-3-title": "المجموعات اللامركزية مُعمَّاة — المستخدمون فقط يعرفون بوجودها", "feature-5-title": "رسائل ذاتية الاختفاء", - "feature-6-title": "تَعْمِيَة المكالمات الصوتية والفيديو <br> بين الطرفين", + "feature-6-title": "تعمية المكالمات الصوتية والفيديو <br> بين الطرفين", "simplex-network-overlay-1-title": "مقارنة مع بروتوكولات المُراسلة P2P", "simplex-private-4-title": "وصول <br> اختياري عبر تور", "simplex-explained-tab-2-text": "2. كيف يعمل", @@ -40,13 +40,13 @@ "hero-2-header": "أجري اتصال خاص", "hero-overlay-1-title": "كيف يعمل SimpleX؟", "hero-2-header-desc": "يُظهر الفيديو كيفية اتصالك بصديقك عبر رمز الاستجابة السريعة لمرة واحدة (QR code)، شخصيًا أو عبر رابط فيديو. يمكنك أيضًا الاتصال من خلال مشاركة رابط الدعوة.", - "feature-1-title": "تَعْمِيَة الرسائل بين الطرفين مع دعم ماركداون والتعديل", - "feature-4-title": "تَعْمِيَة الرسائل الصوتية بين الطرفين", + "feature-1-title": "تعمية الرسائل بين الطرفين مع دعم ماركداون والتعديل", + "feature-4-title": "تعمية الرسائل الصوتية بين الطرفين", "privacy-matters-overlay-card-1-p-1": "تستخدم العديد من الشركات الكبيرة معلومات حول من تتصل به لتقدير دخلك، وبيع المنتجات التي لا تحتاجها حقًا، ولتحديد الأسعار.", "feature-7-title": "تخزين بيانات التطبيق مُعمَّاة ومحمولة — نقل ملف التعريف إلى جهاز آخر", "feature-8-title": "وضع التخفي — <br> فريد من نوعه لـ SimpleX Chat", - "simplex-private-1-title": "طبقتان من <br> التَعْمِيَة بين الطرفين", - "simplex-private-2-title": "طبقة إضافية من <br> تَعْمِيَة الخادم", + "simplex-private-1-title": "طبقتان من <br> التعمية بين الطرفين", + "simplex-private-2-title": "طبقة إضافية من <br> تعمية الخادم", "simplex-private-3-title": "تأمين نقل <br> TLS المصدق عليه", "simplex-private-5-title": "طبقات متعددة من <br> حشوة المحتوى", "simplex-private-7-title": "التحقق <br> من سلامة الرسالة", @@ -56,7 +56,7 @@ "simplex-private-card-3-point-2": "تعمل بصمة الخادم وربط القناة على منع هجمات الوسيط (MITM) وإعادة التشغيل.", "simplex-private-card-3-point-3": "استئناف الاتصال معطل لمنع هجمات الجلسة.", "simplex-private-card-4-point-1": "لحماية عنوان IP الخاص بك، يمكنك الوصول إلى الخوادم عبر تور أو بعض شبكات تراكب النقل الأخرى.", - "simplex-private-card-5-point-1": "يستخدم SimpleX حشوة المحتوى لكل طبقة تَعْمِيَة لإحباط هجمات حجم الرسالة.", + "simplex-private-card-5-point-1": "يستخدم SimpleX حشوة المحتوى لكل طبقة تعمية لإحباط هجمات حجم الرسالة.", "simplex-private-card-6-point-2": "لمنع ذلك، تقوم تطبيقات SimpleX بتمرير مفاتيح لمرة واحدة خارج النطاق، عند مشاركة عنوان كرابط أو رمز QR.", "simplex-private-card-8-point-1": "تعمل خوادم SimpleX كعقد مختلطة بزمن انتقال منخفض — الرسائل الواردة والصادرة لها ترتيب مختلف.", "simplex-private-card-9-point-1": "كل رسالة انتظار تمرر الرسائل في اتجاه واحد، بعناوين إرسال واستلام مختلفة.", @@ -78,7 +78,7 @@ "simplex-unique-4-title": "أنت تمتلك شبكة SimpleX", "simplex-unique-4-overlay-1-title": "لامركزية بالكامل — يمتلك المستخدمون شبكة SimpleX", "hero-overlay-card-1-p-4": "هذا التصميم يمنع تسريب أي البيانات الوصفية للمستخدمين على مستوى التطبيق. لزيادة تحسين الخصوصية وحماية عنوان IP الخاص بك، يمكنك الاتصال بخوادم المراسلة عبر Tor.", - "hero-overlay-card-1-p-5": "الأجهزة العميلة فقط هي التي تخزن ملفات تعريف المستخدمين، جهات الاتصال والمجموعات؛ يتم إرسال الرسائل بتَعْمِيَة ثنائية الطبقة من طرف إلى طرف.", + "hero-overlay-card-1-p-5": "الأجهزة العميلة فقط هي التي تخزن ملفات تعريف المستخدمين، جهات الاتصال والمجموعات؛ يتم إرسال الرسائل بتعمية ثنائية الطبقة بين الطرفين.", "hero-overlay-card-2-p-2": "يمكنهم بعد ذلك ربط هذه المعلومات بالشبكات الاجتماعية العامة الحالية، وتحديد بعض الهويات الحقيقية.", "simplex-network-overlay-card-1-li-3": "P2P لا يحل مشكلة <a href='https://en.wikipedia.org/wiki/Man-in-the-middle_attack'>هجوم الوسيط (MITM)</a>، ومعظم التطبيقات الحالية لا تستخدم خارج النطاق رسائل للتبادل الأولي للمفاتيح. يستخدم SimpleX رسائل خارج النطاق أو، في بعض الحالات، اتصالات آمنة وموثوق بها موجودة مسبقًا لتبادل المفاتيح الأولي.", "privacy-matters-overlay-card-1-p-3": "تستخدم بعض الشركات المالية والتأمين رسوماً بيانية اجتماعية لتحديد أسعار الفائدة وأقساط التأمين. عادةً ما تجعل الأشخاص ذوي الدخل المنخفض يدفعون أكثر — أو كما يسمى<a href='https://fairbydesign.com/povertypremium/' target='_blank'> \"علاوة الفقر\"</a>.", @@ -88,7 +88,7 @@ "privacy-matters-overlay-card-1-p-4": "تحمي منصة SimpleX خصوصية اتصالاتك بشكل أفضل من أي بديل آخر، مما يمنع تمامًا الرسم البياني الاجتماعي الخاص بك من أن يصبح متاحًا لأي شركات أو مؤسسات. حتى عندما يستخدم الأشخاص الخوادم التي توفرها SimpleX Chat، فإننا لا نعرف عدد المستخدمين أو اتصالاتهم.", "simplex-private-card-1-point-1": "بروتوكول السقاطة المزدوجة — <br> رسائل OTR مع السرية المستمرة واستعادة الاختراق.", "simplex-private-card-1-point-2": "NaCL cryptobox في كل قائمة انتظار لمنع ارتباط حركة مرور البيانات بين قوائم انتظار الرسائل في حالة اختراق TLS.", - "simplex-private-card-2-point-1": "طبقة إضافية من تَعْمِيَة الخادم للتسليم إلى المُستلم، لمنع الارتباط بين حركة مرور بيانات الخادم المُستلمة والمُرسلة في حالة اختراق TLS.", + "simplex-private-card-2-point-1": "طبقة إضافية من تعمية الخادم للتسليم إلى المُستلم، لمنع الارتباط بين حركة مرور بيانات الخادم المُستلمة والمُرسلة في حالة اختراق TLS.", "simplex-private-card-4-point-2": "لاستخدام SimpleX عبر تور، يُرجى تثبيت <a href=\"https://guardianproject.info/apps/org.torproject.android/\" target=\"_blank\"> تطبيق Orbot </a> وتمكّين وكيل SOCKS5 (أو VPN <a href=\"https://apps.apple.com/us/app/orbot/id1609461599?platform=iphone\" target=\"_blank\">على iOS </a>).", "simplex-private-card-5-point-2": "يجعل الرسائل ذات الأحجام المختلفة تبدو متشابهة للخوادم ومراقبي الشبكة.", "simplex-private-card-6-point-1": "العديد من منصات الاتصال عرضة لهجمات الوسيط (MITM) من قبل الخوادم أو موفري الشبكات.", @@ -111,11 +111,11 @@ "simplex-network-overlay-card-1-li-4": "يمكن لبعض مزودي خدمة الإنترنت حظر تطبيقات P2P (مثل <a href='https://en.wikipedia.org/wiki/BitTorrent'> BitTorrent </a>). SimpleX حيادي النقل - يمكنه العمل عبر بروتوكولات الويب القياسية، على سبيل المثال WebSockets.", "simplex-network-overlay-card-1-li-5": "قد تكون جميع شبكات P2P المعروفة عرضة <a href='https://en.wikipedia.org/wiki/Sybil_attack'> لهجوم Sybil</a>، لأن كل عقدة قابلة للاكتشاف، وتعمل الشبكة ككل. تتطلب الإجراءات المعروفة لتخفيفها إما مكونًا مركزيًا أو <a href='https://en.wikipedia.org/wiki/Proof_of_work'> إثبات عمل مكلف </a>. لا تحتوي شبكة SimpleX على إمكانية اكتشاف الخادم، فهي مجزأة وتعمل كشبكات فرعية متعددة ومعزولة، مما يجعل الهجمات على مستوى الشبكة مستحيلة.", "privacy-matters-overlay-card-3-p-1": "يجب على الجميع الاهتمام بخصوصية وأمان اتصالاتهم — يمكن للمُحادثات غير الضارة أن تعرضك للخطر، حتى لو لم يكن لديك ما تخفيه.", - "privacy-matters-overlay-card-3-p-4": "لا يكفي استخدام برنامج مُراسلة مُعمَّاة من طرف إلى طرف، يجب علينا جميعًا استخدام برامج مُراسلة التي تحمي خصوصية شبكاتنا الشخصية — مع من نحن مرتبطون.", + "privacy-matters-overlay-card-3-p-4": "لا يكفي استخدام برنامج مُراسلة مُعمَّاة بين الطرفين، يجب علينا جميعًا استخدام برامج مُراسلة التي تحمي خصوصية شبكاتنا الشخصية — مع من نحن مرتبطون.", "simplex-unique-overlay-card-1-p-3": "يحمي هذا التصميم خصوصية الأشخاص الذين تتواصل معهم، ويخفيها عن خوادم منصة SimpleX ومن أي مراقبين. لإخفاء عنوان IP الخاص بك من الخوادم، يمكنك <strong> الاتصال بخوادم SimpleX عبر تور</strong>.", "simplex-unique-overlay-card-2-p-1": "نظرًا لعدم وجود معرف لديك على نظام SimpleX، لا يمكن لأي شخص الاتصال بك ما لم تشارك عنوان مستخدم لمرة واحدة أو مؤقتًا، كرمز QR أو رابط.", "simplex-unique-overlay-card-2-p-2": "حتى مع عنوان المستخدم الاختياري، بينما يمكن استخدامه لإرسال طلبات جهات اتصال مزعجة، يمكنك تغييره أو حذفه بالكامل دون فقد أي من اتصالاتك.", - "simplex-unique-overlay-card-3-p-2": "يتم الاحتفاظ بالرسائل المُعمَّاة من طرف إلى طرف مؤقتًا على خوادم ترحيل SimpleX حتى يتم استلامها، ثُمَّ تُحذف نهائيًا.", + "simplex-unique-overlay-card-3-p-2": "يتم الاحتفاظ بالرسائل المُعمَّاة بين الطرفين مؤقتًا على خوادم ترحيل SimpleX حتى يتم استلامها، ثُمَّ تُحذف نهائيًا.", "simplex-unique-overlay-card-3-p-4": "لا توجد معرفّات أو نص مُعَمَّى مشترك بين حركة مرور بيانات الخادم المُرسلة والمُستلمة — ؛ إذا كان أي شخص يراقب ذلك، فلن يتمكّن بسهولة من تحديد من يتواصل مع من، حتى لو اختُرق TLS.", "simplex-unique-card-1-p-1": "يحمي SimpleX خصوصية ملف التعريف الخاص بك، جهات الاتصال والبيانات الوصفية، ويخفيه عن خوادم منصة SimpleX وأي مراقبين.", "privacy-matters-overlay-card-2-p-1": "منذ وقت ليس ببعيد، لاحظنا أن الانتخابات الرئيسية يتم التلاعب بها بواسطة <a href='https://en.wikipedia.org/wiki/Facebook–Cambridge_Analytica_data_scandal' target='_blank'> شركة استشارية ذات سمعة طيبة </a> التي استخدمت الرسوم البيانية الاجتماعية لتشويه نظرتنا للعالم الحقيقي والتلاعب بأصواتنا.", @@ -161,7 +161,7 @@ "comparison-section-list-point-6": "على الرغم من أن الـP2P موزعة، إلا أنها ليست اتِحاديَّة - يعملون كشبكة واحدة", "comparison-section-list-point-1": "عادة ما يكون مكوناً من رقم الهاتف، أو اسم المستخدم في بعض الأحيان", "comparison-section-list-point-4": "إذا خوادم المشغّل مُخترقة. تحقق من رمز الأمان في Signal وبعض التطبيقات الأخرى للتخفيف منه", - "simplex-unique-card-3-p-1": "يخزن SimpleX جميع بيانات المستخدم على الأجهزة العميلة<strong> بتنسيق قاعدة بيانات محمولة مُعمَّاة — </strong>يمكّن نقله إلى جهاز آخر.", + "simplex-unique-card-3-p-1": "يخزن SimpleX جميع بيانات المستخدم على الأجهزة العميلة<strong> بتنسيق قاعدة بيانات محمولة مُعمَّاة — </strong>يمكّن نقلها إلى جهاز آخر.", "simplex-unique-card-4-p-1": "شبكة SimpleX لا مركزية بالكامل ومستقلة عن أي عملة مُعمَّاة أو أي منصة أخرى، بخلاف الإنترنت.", "simplex-unique-card-4-p-2": "يمكنك<strong> استخدام SimpleX مع خوادمك الخاصة </strong> أو مع الخوادم التي نوفرها — ولا يزال الاتصال ممكن بأي مستخدم.", "join": "انضم إلى", @@ -192,7 +192,7 @@ "protocol-2-text": "XMPP ،Matrix", "simplex-unique-card-1-p-2": "بخلاف أي نظام مُراسلة آخر، لا يحتوي SimpleX على معرّفات مخصصة للمستخدمين —<strong> ولا حتى أرقام عشوائية</strong>.", "simplex-unique-card-2-p-1": "نظرًا لعدم وجود معرف أو عنوان ثابت على منصة SimpleX، لا يمكن لأي شخص الاتصال بك ما لم تشارك عنوان مستخدم لمرة واحدة أو مؤقتًا، كرمز QR أو رابط.", - "simplex-unique-card-3-p-2": "يتم الاحتفاظ بالرسائل المُعمَّاة من طرف إلى طرف مؤقتًا على خوادم ترحيل SimpleX حتى يتم استلامها، ثُمَّ تُحذف نهائيًا.", + "simplex-unique-card-3-p-2": "يتم الاحتفاظ بالرسائل المُعمَّاة بين الطرفين مؤقتًا على خوادم ترحيل SimpleX حتى يتم استلامها، ثُمَّ تُحذف نهائيًا.", "tap-the-connect-button-in-the-app": "اضغط على زر<span class='text-active-blue'> \"اتصال\"</span> في التطبيق", "scan-the-qr-code-with-the-simplex-chat-app": "امسح رمز QR باستخدام تطبيق SimpleX Chat", "scan-the-qr-code-with-the-simplex-chat-app-description": "لا يتم إرسال المفاتيح العامة وعنوان قائمة انتظار الرسائل في هذا الارتباط عبر الشبكة عند عرض هذه الصفحة —<br> فهي موجودة في جزء التجزئة لعنوان URL للرابط.", @@ -243,12 +243,14 @@ "releases-to-this-repo-are-done-1-2-days-later": "يتم إصدار الإصدارات إلى هذا المستودع بعد يوم أو يومين", "f-droid-page-simplex-chat-repo-section-text": "لإضافته إلى عميل F-Droid، <span class='hide-on-mobile'>امسح رمز QR أو</span> استخدم عنوان URL هذا:", "f-droid-page-f-droid-org-repo-section-text": "مستودعات SimpleX Chat و F-Droid.org مبنية على مفاتيح مختلفة. للتبديل، يُرجى <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>تصدير</a> قاعدة بيانات الدردشة وإعادة تثبيت التطبيق.", - "comparison-section-list-point-4a": "مُرحلات SimpleX لا يمكنها أن تتنازل عن تَعْمِيَة بين الطرفين. تحقق من رمز الأمان للتخفيف من الهجوم على القناة خارج النطاق", + "comparison-section-list-point-4a": "مُرحلات SimpleX لا يمكنها أن تتنازل عن تعمية بين الطرفين. تحقق من رمز الأمان للتخفيف من الهجوم على القناة خارج النطاق", "hero-overlay-3-title": "التقييم الأمني", "hero-overlay-card-3-p-2": "قامت Trail of Bits بمراجعة مكونات التشفير والشبكات الخاصة بمنصة SimpleX في نوفمبر 2022.", "hero-overlay-card-3-p-3": "اقرأ المزيد في <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">الإعلان</a>.", "jobs": "انضم للفريق", "hero-overlay-3-textlink": "التقييم الأمني", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> هي شركة رائدة في مجال الاستشارات الأمنية والتكنولوجية، ومن بين عملائها شركات التكنولوجيا الكبرى والوكالات الحكومية ومشاريع blockchain الكبرى.", - "docs-dropdown-9": "التنزيلات" + "docs-dropdown-9": "التنزيلات", + "please-enable-javascript": "الرجاء تفعيل جافا سكريبت (JavaScript) لرؤية رمز QR.", + "please-use-link-in-mobile-app": "يُرجى استخدام الرابط في تطبيق الجوال" } diff --git a/website/langs/bg.json b/website/langs/bg.json index 8ab6d958ed..b84d369678 100644 --- a/website/langs/bg.json +++ b/website/langs/bg.json @@ -1,3 +1,6 @@ { - "developers": "Разработчици" + "developers": "Разработчици", + "glossary": "Речник", + "yes": "Да", + "no": "Не" } diff --git a/website/langs/cs.json b/website/langs/cs.json index d087fd5bec..6e2416e4a2 100644 --- a/website/langs/cs.json +++ b/website/langs/cs.json @@ -37,10 +37,10 @@ "hero-overlay-1-title": "Jak funguje SimpleX?", "hero-overlay-2-title": "Proč jsou uživatelská ID špatná pro soukromí?", "feature-1-title": "E2E šifrované zprávy s Markdown a editací", - "feature-3-title": "Decentralizované tajné skupiny —<br>pouze uživatelé vědí, že existují", + "feature-3-title": "E2E-šifrované decentralizované skupiny — pouze uživatelé vědí, že existují", "feature-4-title": "Hlasové zprávy šifrované E2E", "feature-6-title": "E2E šifrované<br>hlasové a videohovory", - "feature-7-title": "Přenosná šifrovaná databáze— přeneste svůj profil do jiného zařízení", + "feature-7-title": "Přenosné šifrované ůložiště aplikace — přeneste profil do jiného zařízení", "feature-8-title": "režim Inkognito —<br>Unikátní pro SimpleX Chat", "simplex-network-overlay-1-title": "Srovnání s protokoly zpráv P2P", "simplex-private-2-title": "Další vrstva<br>šifrování serveru", @@ -170,8 +170,8 @@ "comparison-section-list-point-1": "Založeném obvykle na telefonním čísle a v některých případech uživatelském jménu", "comparison-section-list-point-2": "Adresy založené na DNS", "comparison-section-list-point-3": "Veřejný klíč nebo jiné globálně jedinečné ID", - "comparison-section-list-point-4": "Pokud jsou servery operátora ohroženy", - "comparison-section-list-point-5": "Nechrání metadata uživatelů", + "comparison-section-list-point-4": "Pokud jsou servery operátora kompromitovány. Ověřte bezpečnostní kód v Signálu a nějaké jiné aplikaci ke zmírnění", + "comparison-section-list-point-5": "Nechrání soukromí metadat uživatelů", "comparison-section-list-point-6": "Zatímco P2P jsou distribuovány, nejsou federované – fungují jako jediná síť", "comparison-section-list-point-7": "P2P sítě mají buď centrální autoritu, nebo může být ohrožena celá síť", "see-here": "viz zde", @@ -195,7 +195,7 @@ "simplex-explained-tab-3-p-2": "Uživatelé mohou dále zlepšit soukromí metadat pomocí Tor pro přístup k serverům, což zabraňuje korelaci podle IP adresy.", "hero-p-1": "Jiné aplikace mají uživatelská ID: Signal, Matrix, Session, Briar, Jami, Cwtch atd.<br> SimpleX ne, <strong>ani náhodná čísla</strong>.<br> To radikálně zlepšuje vaše soukromí.", "hero-2-header-desc": "Video ukazuje, jak se spojit se svým přítelem prostřednictvím jeho jednorázového QR kódu, osobně nebo prostřednictvím QR kódu ve videu. Můžete se také připojit sdílením pozvánky.", - "feature-2-title": "Obrázky a soubory šifrované E2E<br>", + "feature-2-title": "E2E šifrované<br>obrázky, videa a soubory", "feature-5-title": "Mizící tajné konverzace", "simplex-private-1-title": "2-vrstvé<br>end-to-end šifrování", "simplex-private-8-title": "Smíchání zpráv<br>ke snížení korelace", @@ -232,5 +232,25 @@ "on-this-page": "Na této stránce", "back-to-top": "Zpět nahoru", "newer-version-of-eng-msg": "V angličtině je novější verze této stránky.", - "glossary": "Rejstřík" + "glossary": "Rejstřík", + "hero-overlay-3-title": "Posouzení bezpečnosti", + "hero-overlay-3-textlink": "Posouzení bezpečnosti", + "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> je přední bezpečnostní a technologické poradenství, jejichž klienti zahrnují velké technologické firmy, vládní agentury a významné blockchainové projekty.", + "f-droid-page-simplex-chat-repo-section-text": "Chcete-li jej přidat do vašeho F-Droid clienta, <span class='hide-on-mobile'> naskenujte QR kód nebo</span> použijte tuto adresu URL:", + "f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat a F-Droid.org repozitáře jsou podepsané různými klíči. Chcete-li přepnout, prosím <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>exportujte</a> chat databázi a přeinstalujte aplikaci.", + "comparison-section-list-point-4a": "SimpleX relé nemůže ohrozit šifrování e2e. Ověřte bezpečnostní kód, který zmírňuje mimo pásmový útok na kanál", + "docs-dropdown-8": "Služba SimpleX Directory", + "please-enable-javascript": "Prosím, povolte JavaScript k zobrazení QR kódu.", + "please-use-link-in-mobile-app": "Prosím použijte odkaz v mobilní aplikaci", + "simplex-chat-via-f-droid": "SimpleX Chat přes F-Droid", + "simplex-chat-repo": "SimpleX Chat repozitář", + "stable-and-beta-versions-built-by-developers": "Stabilní a beta verze vytvořené vývojáři", + "signing-key-fingerprint": "Otisk podpisového klíče (SHA-256)", + "f-droid-org-repo": "F-Droid.org repozitář", + "stable-versions-built-by-f-droid-org": "Stabilní verze vytvořené F-Droid.org", + "releases-to-this-repo-are-done-1-2-days-later": "Vydání v tomto repozitáři se provádí o 1-2 dny později", + "jobs": "Připojit k týmu", + "hero-overlay-card-3-p-2": "Trail of Bits přezkoumala kryptografii a síťové komponenty SimpleX platformy v listopadu 2022.", + "hero-overlay-card-3-p-3": "Přečtěte si více v <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">ohlášení</a>.", + "docs-dropdown-9": "Ke stažení" } diff --git a/website/langs/de.json b/website/langs/de.json index cb03588e1f..7b37869429 100644 --- a/website/langs/de.json +++ b/website/langs/de.json @@ -250,5 +250,7 @@ "jobs": "Treten Sie dem Team bei", "hero-overlay-3-textlink": "Sicherheits-Gutachten", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> ist eine führende Security- und Technologie-Unternehmensberatung, deren Kunden aus den Bereichen Big-Tech, Regierungsbehörden und großen Blockchain-Projekten stammen.", - "docs-dropdown-9": "Downloads" + "docs-dropdown-9": "Downloads", + "please-enable-javascript": "Bitte aktivieren Sie JavaScript, um den QR-Code zu sehen.", + "please-use-link-in-mobile-app": "Bitte nutzen Sie den Link in der Mobiltelefon-App" } diff --git a/website/langs/es.json b/website/langs/es.json index 9b116b4215..d235a030fb 100644 --- a/website/langs/es.json +++ b/website/langs/es.json @@ -250,5 +250,7 @@ "jobs": "Únete al equipo", "hero-overlay-3-textlink": "Evaluación de la seguridad", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> es una consultora de seguridad y tecnología líder cuyos clientes incluyen grandes tecnológicas, agencias gubernamentales e importantes proyectos de blockchain.", - "docs-dropdown-9": "Descargas" + "docs-dropdown-9": "Descargas", + "please-enable-javascript": "Habilita JavaScript para ver el código QR.", + "please-use-link-in-mobile-app": "Usa el enlace en la apliación móvil," } diff --git a/website/langs/fi.json b/website/langs/fi.json index 963ed42c87..1d704cc8ec 100644 --- a/website/langs/fi.json +++ b/website/langs/fi.json @@ -27,7 +27,7 @@ "hero-overlay-1-title": "Kuinka SimpleX toimii?", "hero-overlay-2-title": "Miksi käyttäjätunnukset ovat huonoja yksityisyydelle?", "feature-1-title": "Päästä päähän salattuja viestejä markdownin ja muokkaamisen kera", - "feature-2-title": "Päästä päähän salattuja<br>kuvia ja tiedostoja", + "feature-2-title": "Päästä päähän salattuja<br>kuvia, videoita ja tiedostoja", "feature-3-title": "Hajautetut salaiset ryhmät —<br>vain käyttäjät tietävät niiden olemassaolosta", "feature-4-title": "Päästä päähän salattuja ääniviestejä", "feature-5-title": "Katoavia viestejä", @@ -243,5 +243,7 @@ "menu": "Valikko", "simplex-chat-via-f-droid": "SimpleX Chat F-Droidin kautta", "stable-and-beta-versions-built-by-developers": "Kehittäjien luomat vakaat ja beta-versiot", - "f-droid-page-simplex-chat-repo-section-text": "Lisätäksesi sen F-Droid-asiakkaaseesi, <span class='hide-on-mobile'>skannaa QR-koodi tai</span> käytä tätä URL-osoitetta:" + "f-droid-page-simplex-chat-repo-section-text": "Lisätäksesi sen F-Droid-asiakkaaseesi, <span class='hide-on-mobile'>skannaa QR-koodi tai</span> käytä tätä URL-osoitetta:", + "jobs": "Liity tiimiin", + "docs-dropdown-9": "Lataukset" } diff --git a/website/langs/fr.json b/website/langs/fr.json index 2cdf1b9ae9..580f4134cd 100644 --- a/website/langs/fr.json +++ b/website/langs/fr.json @@ -251,5 +251,7 @@ "jobs": "Rejoignez notre équipe", "hero-overlay-3-textlink": "Évaluation de sécurité", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> est un cabinet leader dans le secteur de la sécurité et des technologies qui compte parmi ses clients des grandes entreprises de la tech, des agences gouvernementales et d'importants projets de blockchain.", - "docs-dropdown-9": "Téléchargements" + "docs-dropdown-9": "Téléchargements", + "please-enable-javascript": "Veuillez activer JavaScript pour voir le code QR.", + "please-use-link-in-mobile-app": "Veuillez utiliser le lien dans l'application mobile" } diff --git a/website/langs/it.json b/website/langs/it.json index 3dda11e9c2..388f461374 100644 --- a/website/langs/it.json +++ b/website/langs/it.json @@ -250,5 +250,7 @@ "jobs": "Unisciti al team", "hero-overlay-3-textlink": "Valutazione della sicurezza", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> è leader nella consulenza di sicurezza e tecnologia, i cui clienti includono grandi aziende, agenzie governative e importanti progetti di blockchain.", - "docs-dropdown-9": "Download" + "docs-dropdown-9": "Download", + "please-enable-javascript": "Attiva JavaScript per vedere il codice QR.", + "please-use-link-in-mobile-app": "Usa il link nell'app mobile" } diff --git a/website/langs/ja.json b/website/langs/ja.json index 2b6d657608..8fa5cac1f9 100644 --- a/website/langs/ja.json +++ b/website/langs/ja.json @@ -250,5 +250,7 @@ "hero-overlay-3-textlink": "セキュリティ監査", "hero-overlay-3-title": "セキュリティ監査", "hero-overlay-card-3-p-2": "Trail of Bitsは2022年11月にSimpleXプラットフォームの暗号とネットワークのコンポーネントを検証しました。", - "docs-dropdown-9": "ダウンロード" -} \ No newline at end of file + "docs-dropdown-9": "ダウンロード", + "please-enable-javascript": "QRコードを表示するためにJavaScriptを有効にしてください。", + "please-use-link-in-mobile-app": "このリンクをモバイルアプリで使用してください" +} diff --git a/website/langs/nl.json b/website/langs/nl.json index 51c5855b51..847baf4c3a 100644 --- a/website/langs/nl.json +++ b/website/langs/nl.json @@ -250,5 +250,7 @@ "jobs": "Sluit je aan bij het team", "hero-overlay-3-textlink": "Beveiligings beoordeling", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> is een toonaangevend beveiligings- en technologieadviesbureau met klanten onder meer grote technologiebedrijven, overheidsinstanties en grote blockchain-projecten.", - "docs-dropdown-9": "Downloads" + "docs-dropdown-9": "Downloads", + "please-enable-javascript": "Schakel JavaScript in om de QR-code te zien.", + "please-use-link-in-mobile-app": "Gebruik de link in de mobiele app" } diff --git a/website/langs/pl.json b/website/langs/pl.json index ba0c72da7e..041176a1e1 100644 --- a/website/langs/pl.json +++ b/website/langs/pl.json @@ -45,10 +45,10 @@ "home": "Strona główna", "developers": "Deweloperzy", "simplex-explained-tab-1-text": "1. Jakie są doświadczenia użytkowników", - "simplex-explained-tab-1-p-1": "Można tworzyć kontakty i grupy oraz prowadzić dwukierunkowe rozmowy, jak w każdym innym komunikatorze.", + "simplex-explained-tab-1-p-1": "Możesz tworzyć kontakty i grupy oraz prowadzić dwukierunkowe rozmowy, jak w każdym innym komunikatorze.", "simplex-explained-tab-2-p-2": "Serwery przekazują wiadomości tylko w jedną stronę, nie mając pełnego obrazu konwersacji i połączeń użytkownika.", "simplex-explained-tab-3-p-2": "Użytkownicy mogą jeszcze bardziej zwiększyć prywatność metadanych, używając Tor do uzyskania dostępu do serwerów, co zapobiega korelacji na podstawie adresu IP.", - "hero-p-1": "Inne aplikacje mają identyfikatory użytkowników (ID): Signal, Matrix, Session, Briar, Jami, Cwtch itp.<br> SimpleX nie, <strong>nie ma nawet losowych numerów</strong>.<br> To radykalnie poprawia Twoją prywatność.", + "hero-p-1": "Inne aplikacje posiadają identyfikatory użytkowników (ID): Signal, Matrix, Session, Briar, Jami, Cwtch itp.<br> SimpleX nie, <strong>nie posiada nawet losowych numerów</strong>.<br> To radykalnie poprawia Twoją prywatność.", "feature-2-title": "Obrazy, wideo i pliki<br>zaszyfrowane przez E2E", "feature-8-title": "Tryb incognito —<br>unikalny dla SimpleX Chat", "simplex-network-overlay-1-title": "Porównanie z protokołami komunikacyjnymi P2P", @@ -250,5 +250,7 @@ "jobs": "Dołącz do zespołu", "hero-overlay-3-textlink": "Ocena bezpieczeństwa", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> jest wiodącą firmą konsultingową w zakresie bezpieczeństwa i technologii, której klientami są duże firmy technologiczne, agencje rządowe i główne projekty blockchain.", - "docs-dropdown-9": "Pliki do pobrania" + "docs-dropdown-9": "Pliki do pobrania", + "please-enable-javascript": "Prosimy o włączenie JavaScript w celu wyświetlenia kodu QR.", + "please-use-link-in-mobile-app": "Prosimy o skorzystanie z linku w aplikacji mobilnej" } diff --git a/website/langs/pt_BR.json b/website/langs/pt_BR.json index 278d54c43a..1bb21372e6 100644 --- a/website/langs/pt_BR.json +++ b/website/langs/pt_BR.json @@ -13,20 +13,20 @@ "hero-p-1": "Outros aplicativos possuem IDs de usuário: Signal, Matrix, Session, Briar, Jami, Cwtch, etc.<br> O SimpleX não tem, <strong>nem mesmo números aleatórios</strong>.<br> Isso melhora radicalmente a sua privacidade.", "terms-and-privacy-policy": "Termos e Política de Privacidade", "hero-2-header-desc": "O vídeo mostra como você se conecta com seu amigo através do QR code de uso único dele, pessoalmente ou via link de vídeo. Você também pode se conectar compartilhando um link de convite.", - "feature-7-title": "Banco de dados criptografado portátil — mova seu perfil para outro dispositivo", + "feature-7-title": "Armazenamento do aplicativo criptografado portátil — mova o perfil para outro dispositivo", "simplex-explained": "Explicação do SimpleX", "simplex-explained-tab-1-text": "1. O que os usuários experimentam", "simplex-explained-tab-3-text": "3. O que os servidores veem", "simplex-explained-tab-1-p-2": "Como ele pode funcionar com filas unidirecionais e sem identificadores de perfil de usuário?", "simplex-explained-tab-2-p-2": "Os servidores só passam mensagens em uma direção, sem ter a imagem completa da conversa ou conexões do usuário.", "simplex-explained-tab-3-p-1": "Os servidores têm credenciais anônimas separadas para cada envio e não sabem a que usuários elas pertencem.", - "simplex-explained-tab-3-p-2": "Os usuários podem melhorar ainda mais a privacidade de metadados usando o Tor para acessar os servidores, impedindo a correlação pelo endereço IP.", + "simplex-explained-tab-3-p-2": "Os usuários podem melhorar ainda mais a privacidade de metadados usando o Tor para acessar os servidores, impedindo a correlação pelo endereço de IP.", "chat-bot-example": "Exemplo de chat bot", "smp-protocol": "Protocolo SMP", - "chat-protocol": "Protocolo de chat", + "chat-protocol": "Protocolo de bate-papo", "donate": "Doar", - "copyright-label": "© 2020-2023 SimpleX | Projeto de código livre", - "simplex-chat-protocol": "Protocolo de Chat SimpleX", + "copyright-label": "© 2020-2023 SimpleX | Projeto de Código Livre", + "simplex-chat-protocol": "Protocolo Chat SimpleX", "terminal-cli": "CLI Terminal", "hero-header": "Privacidade redefinida", "hero-subheader": "O primeiro mensageiro<br>sem ID de usuário", @@ -36,8 +36,8 @@ "hero-overlay-1-textlink": "Por que os IDs são ruins para a privacidade?", "hero-overlay-2-title": "Por que os IDs são ruins para a privacidade?", "feature-1-title": "Mensagens criptografadas de ponta-a-ponta com markdown e edição", - "feature-2-title": "Imagens e arquivos<br>criptografados de ponta-a-ponta", - "feature-3-title": "Grupos secretos descentralizados —<br>somente os usuários sabem que eles existem", + "feature-2-title": "Criptografia de ponta-a-ponta<br>imagens, vídeos e arquivos", + "feature-3-title": "Criptografia de ponta-a-ponta descentralizada de grupos — somente os usuários sabem que eles existem", "feature-4-title": "Mensagens de voz criptografadas de ponta-a-ponta", "feature-5-title": "Mensagens que desaparecem", "feature-6-title": "Chamadas de áudio e vídeo<br>criptografadas de ponta-a-ponta", @@ -65,13 +65,13 @@ "privacy-matters-overlay-card-1-p-2": "Os varejistas online sabem que as pessoas com renda mais baixa têm maior probabilidade de fazer compras urgentes e, por isso, podem cobrar preços mais altos ou remover descontos.", "privacy-matters-overlay-card-1-p-4": "A plataforma SimpleX protege a privacidade de suas conexões melhor do que qualquer alternativa, impedindo totalmente que seu gráfico social fique disponível para quaisquer empresas ou organizações. Mesmo quando as pessoas usam servidores fornecidos pelo SimpleX Chat, não sabemos o número de usuários ou suas conexões.", "privacy-matters-overlay-card-2-p-2": "Para ser objetivo e tomar decisões independentes, você precisa ter o controle do seu espaço de informações. Isso só é possível se você usar uma plataforma de comunicação privada que não tenha acesso ao seu gráfico social.", - "privacy-matters-overlay-card-3-p-1": "Todos devem se preocupar com a privacidade e a segurança de suas comunicações - conversas inofensivas podem colocá-lo em perigo, mesmo que você não tenha nada a esconder.", + "privacy-matters-overlay-card-3-p-1": "Todos devem se preocupar com a privacidade e a segurança de suas comunicações — conversas inofensivas podem colocá-lo em perigo, mesmo que você não tenha nada a esconder.", "simplex-unique-overlay-card-3-p-1": "O SimpleX Chat armazena todos os dados do usuário somente em dispositivos clientes usando um <strong>formato de banco de dados criptografado portátil</strong> que pode ser exportado e transferido para qualquer dispositivo compatível.", - "privacy-matters-overlay-card-3-p-2": "Uma das histórias mais chocantes é a experiência de <a href='https://pt.wikipedia.org/wiki/Mohamedou_Ould_Slahi' target='_blank'>Mohamedou Ould Salahi</a>, descrita em seu livro de memórias e mostrada no filme O Mauritano. Ele foi colocado no campo de Guantánamo, sem julgamento, e lá foi torturado por 15 anos após um telefonema para seu parente no Afeganistão, sob suspeita de estar envolvido nos ataques de 11 de setembro, embora tenha vivido na Alemanha nos 10 anos anteriores.", - "simplex-unique-overlay-card-1-p-1": "Ao contrário de outras plataformas de mensagens, o SimpleX <strong>não tem identificadores atribuídos aos usuários</strong>. Ele não depende de números de telefone, endereços baseados em domínio (como e-mail ou XMPP), nomes de usuário, chaves públicas ou mesmo números aleatórios para identificar seus usuários — não sabemos quantas pessoas usam nossos servidores SimpleX.", - "simplex-private-card-1-point-1": "Protocolo de dupla catraca —<br>mensagens OTR com sigilo de encaminhamento perfeito e recuperação de invasão.", + "privacy-matters-overlay-card-3-p-2": "Uma das histórias mais chocantes é a experiência de <a href='https://en.wikipedia.org/wiki/Mohamedou_Ould_Slahi' target='_blank'>Mohamedou Ould Salahi</a>, descrita em seu livro de memórias e mostrada no filme O Mauritano. Ele foi colocado no campo de Guantánamo, sem julgamento, e lá foi torturado por 15 anos após um telefonema para seu parente no Afeganistão, sob suspeita de estar envolvido nos ataques de 11 de setembro, embora tenha vivido na Alemanha nos 10 anos anteriores.", + "simplex-unique-overlay-card-1-p-1": "Ao contrário de outras plataformas de mensagens, o SimpleX <strong>não tem identificadores atribuídos aos usuários</strong>. Ele não depende de números de telefone, endereços baseados em domínio (como email ou XMPP), nomes de usuário, chaves públicas ou mesmo números aleatórios para identificar seus usuários — não sabemos quantas pessoas usam nossos servidores SimpleX.", + "simplex-private-card-1-point-1": "Protocolo de dupla catraca —<br>mensagens OTR com Sigilo de Encaminhamento Perfeito (Perfect Forward Secrecy) e recuperação de invasão.", "simplex-private-8-title": "Mistura de mensagens<br>para reduzir a correlação", - "simplex-unique-overlay-card-4-p-2": "A plataforma SimpleX usa um <a href='https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md' target='_blank'>protocolo aberto</a> e fornece um <a href='https://github.com/simplex-chat/simplex-chat/tree/stable/packages/simplex-chat-client/typescript' target='_blank'>SDK para criar bots de chat</a>, permitindo a implementação de serviços com os quais os usuários podem interagir por meio dos aplicativos SimpleX Chat — estamos realmente ansiosos para ver quais serviços SimpleX você pode criar.", + "simplex-unique-overlay-card-4-p-2": "A plataforma SimpleX usa um <a href='https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md' target='_blank'>protocolo aberto</a> e fornece um <a href='https://github.com/simplex-chat/simplex-chat/tree/stable/packages/simplex-chat-client/typescript' target='_blank'>SDK para criar bots de chat</a>, permitindo a implementação de serviços com os quais os usuários podem interagir por meio dos aplicativos SimpleX Chat — estamos' realmente ansiosos para ver quais serviços SimpleX você pode criar.", "simplex-unique-overlay-card-3-p-4": "Não há identificadores ou texto cifrado em comum entre o tráfego de servidor enviado e recebido — se alguém estiver observando, não poderá determinar facilmente quem se comunica com quem, mesmo que o TLS esteja comprometido.", "simplex-unique-overlay-card-4-p-3": "Se estiver pensando em desenvolver para a plataforma SimpleX, por exemplo, o bot de chat para os usuários do aplicativo SimpleX ou a integração da biblioteca SimpleX Chat em seus aplicativos móveis, <a href='https://simplex.chat/contact#/?v=1&smp=smp%3A%2F%2FPQUV2eL0t7OStZOoAsPEV2QYWt4-xilbakvGUGOItUo%3D%40smp6.simplex.im%2FK1rslx-m5bpXVIdMZg9NLUZ_8JBm8xTt%23MCowBQYDK2VuAyEALDeVe-sG8mRY22LsXlPgiwTNs9dbiLrNuA7f3ZMAJ2w%3D' target='_blank'>entre em contato</a> para qualquer orientação e suporte.", "simplex-unique-card-2-p-1": "Como você não tem um identificador ou endereço fixo na plataforma SimpleX, ninguém pode entrar em contato com você, a menos que você compartilhe um endereço de usuário único ou temporário, como um QR code ou um link.", @@ -81,7 +81,7 @@ "contact-hero-p-3": "Use os links abaixo para baixar o aplicativo.", "feature-8-title": "Modo anônimo —<br> único do SimpleX Chat", "simplex-network-overlay-1-title": "Comparação com protocolos de mensagens P2P", - "simplex-private-2-title": "Camada adicional de<br>encriptação do servidor", + "simplex-private-2-title": "Camada adicional de<br>criptografia do servidor", "simplex-private-4-title": "Acesso opcional<br>via Tor", "simplex-private-6-title": "Troca de chaves<br>fora da rede", "simplex-private-7-title": "Verificação de integridade<br>da mensagem", @@ -91,7 +91,7 @@ "simplex-private-card-3-point-1": "Somente o TLS 1.2/1.3 com algoritmos fortes é usado para conexões cliente-servidor.", "simplex-private-card-3-point-2": "A impressão digital do servidor e a vinculação de canais evitam ataques MITM e de repetição.", "simplex-private-card-3-point-3": "A retomada da conexão é desativada para evitar ataques à sessão.", - "simplex-private-card-5-point-1": "O SimpleX usa proteção de conteúdo em cada camada de criptografia para impedir ataques ao tamanho da mensagem.", + "simplex-private-card-5-point-1": "O SimpleX usa preenchimento de conteúdo em cada camada de criptografia para impedir ataques ao tamanho da mensagem.", "simplex-private-5-title": "Múltiplas camadas de<br>preenchimento de conteúdos", "simplex-private-card-5-point-2": "Isso faz com que mensagens de tamanhos diferentes tenham a mesma aparência para os servidores e observadores de rede.", "simplex-private-card-6-point-1": "Muitas plataformas de comunicação são vulneráveis a ataques MITM por servidores ou provedores de rede.", @@ -117,22 +117,22 @@ "hero-overlay-card-1-p-4": "Esse design evita o vazamento de metadados de qualquer usuário' no nível do aplicativo. Para aumentar ainda mais a privacidade e proteger seu endereço IP, você pode se conectar aos servidores de mensagens via Tor.", "hero-overlay-card-1-p-6": "Leia mais no <a href='https://github.com/simplex-chat/simplexmq/blob/stable/protocol/overview-tjr.md' target='_blank'>informativo técnico do SimpleX</a>.", "hero-overlay-card-2-p-2": "Eles poderiam então correlacionar essas informações com as redes sociais públicas existentes e determinar algumas identidades reais.", - "hero-overlay-card-2-p-4": "O SimpleX se protege contra esses ataques por não ter nenhum ID de usuário em seu design. E, se você usar o modo Incognito, terá um nome de exibição diferente para cada contato, evitando qualquer compartilhamento de dados entre eles.", + "hero-overlay-card-2-p-4": "O SimpleX se protege contra esses ataques por não ter nenhum ID de usuário em seu design. E, se você usar o modo Anônimo, terá um nome de exibição diferente para cada contato, evitando qualquer compartilhamento de dados entre eles.", "simplex-network-overlay-card-1-p-1": "Os protocolos e aplicativos de mensagens <a href='https://pt.wikipedia.org/wiki/Peer-to-peer'>P2P</a> têm vários problemas que os tornam menos confiáveis do que o SimpleX, mais complexos de analisar e vulneráveis a vários tipos de ataque.", "simplex-network-overlay-card-1-li-2": "O design do SimpleX, ao contrário da maioria das redes P2P, não tem identificadores de usuário globais de qualquer tipo, mesmo temporários, e usa apenas identificadores temporários em pares, proporcionando melhor anonimato e proteção de metadados.", "simplex-network-overlay-card-1-li-3": "O P2P não resolve o problema do <a href='https://pt.wikipedia.org/wiki/Ataque_man-in-the-middle'>ataque MITM</a>, e a maioria das implementações existentes não usa mensagens fora de banda para a troca de chaves inicial. O SimpleX usa mensagens fora de banda ou, em alguns casos, conexões pré-existentes seguras e confiáveis para a troca de chaves inicial.", "simplex-network-overlay-card-1-li-6": "As redes P2P podem ser vulneráveis a <a href='https://www.usenix.org/conference/woot15/workshop-program/presentation/p2p-file-sharing-hell-exploiting-bittorrent'>ataques DRDoS</a>, quando os clientes podem retransmitir e amplificar o tráfego, resultando em uma negação de serviço em toda a rede. Os clientes SimpleX apenas retransmitem o tráfego de uma conexão conhecida e não podem ser usados por um invasor para amplificar o tráfego em toda a rede.", "privacy-matters-overlay-card-1-p-3": "Algumas empresas financeiras e de seguros usam gráficos sociais para determinar taxas de juros e prêmios. Isso geralmente faz com que as pessoas com renda mais baixa paguem mais — isso é conhecido como <a href='https://fairbydesign.com/povertypremium/' target='_blank'>\"prêmio de pobreza\"</a>.", - "privacy-matters-overlay-card-2-p-1": "Não faz muito tempo que observamos as eleições sendo manipuladas por <a href='https://pt.wikipedia.org/wiki/Esc%C3%A2ndalo_de_dados_Facebook%E2%80%93Cambridge_Analytica' target='_blank'>uma empresa de consultoria respeitável</a> que usou nossos gráficos sociais para distorcer nossa visão do mundo real e manipular nossos votos.", + "privacy-matters-overlay-card-2-p-1": "Não faz muito tempo que observamos as eleições sendo manipuladas por <a href='https://en.wikipedia.org/wiki/Facebook–Cambridge_Analytica_data_scandal' target='_blank'>uma empresa de consultoria respeitável</a> que usou nossos gráficos sociais para distorcer nossa visão do mundo real e manipular nossos votos.", "privacy-matters-overlay-card-2-p-3": "O SimpleX é a primeira plataforma que não tem nenhum identificador de usuário por design, protegendo assim seu gráfico de conexões melhor do que qualquer alternativa conhecida.", "privacy-matters-overlay-card-3-p-3": "Pessoas comuns são presas pelo que compartilham online, mesmo por meio de suas contas \"anônimas\", <a href='https://www.dailymail.co.uk/news/article-11282263/Moment-police-swoop-house-devout-catholic-mother-malicious-online-posts.html' target='_blank'>mesmo em países democráticos</a>.", "privacy-matters-overlay-card-3-p-4": "Não basta usar um mensageiro criptografado de ponta-a-ponta, todos nós devemos usar os mensageiros que protegem a privacidade de nossas redes pessoais — com quem estamos conectados.", - "simplex-unique-overlay-card-1-p-2": "Para entregar mensagens, o SimpleX usa <a href='https://csrc.nist.gov/glossary/term/Pairwise_Pseudonymous_Identifier'>endereços anônimos em pares</a> de filas de mensagens unidirecionais, separadas para mensagens recebidas e enviadas, geralmente por meio de servidores diferentes. Usar o SimpleX é como ter <strong>um e-mail ou telefone de “gravação” diferente para cada contato</strong>, sem a necessidade de gerenciá-los.", + "simplex-unique-overlay-card-1-p-2": "Para entregar mensagens, o SimpleX usa <a href='https://csrc.nist.gov/glossary/term/Pairwise_Pseudonymous_Identifier'>endereços anônimos em pares</a> de filas de mensagens unidirecionais, separadas para mensagens recebidas e enviadas, geralmente por meio de servidores diferentes. Usar o SimpleX é como ter <strong>um email ou telefone de “gravação” diferente para cada contato</strong>, sem a necessidade de gerenciá-los.", "simplex-unique-overlay-card-1-p-3": "Esse design protege a privacidade de quem está se comunicando com você, ocultando-a dos servidores da plataforma SimpleX e de quaisquer observadores. Para ocultar seu endereço IP dos servidores, você pode <strong>se conectar aos servidores do SimpleX via Tor</strong>.", "simplex-unique-overlay-card-2-p-2": "Mesmo com o endereço de usuário opcional, embora ele possa ser usado para enviar solicitações de contato de spam, você pode alterá-lo ou excluí-lo completamente sem perder nenhuma das suas conexões.", "simplex-unique-overlay-card-2-p-1": "Como você não tem um identificador na plataforma SimpleX, ninguém pode entrar em contato com você, a menos que compartilhe um endereço de usuário único ou temporário, como um QR code ou um link.", "simplex-unique-overlay-card-3-p-2": "As mensagens criptografadas de ponta-a-ponta são mantidas temporariamente nos servidores de retransmissão SimpleX até serem recebidas e, em seguida, são excluídas permanentemente.", - "simplex-unique-overlay-card-3-p-3": "Diferente dos servidores de redes federadas (e-mail, XMPP ou Matrix), os servidores SimpleX não armazenam contas de usuários, apenas retransmitem mensagens, protegendo a privacidade de ambas as partes.", + "simplex-unique-overlay-card-3-p-3": "Diferente dos servidores de redes federadas (email, XMPP ou Matrix), os servidores SimpleX não armazenam contas de usuários, apenas retransmitem mensagens, protegendo a privacidade de ambas as partes.", "simplex-unique-overlay-card-4-p-1": "Você pode <strong>usar o SimpleX com seus próprios servidores</strong> e ainda se comunicar com pessoas que usam os servidores pré-configurados fornecidos por nós.", "simplex-unique-card-1-p-1": "O SimpleX protege a privacidade do seu perfil, contatos e metadados, ocultando-os dos servidores da plataforma SimpleX e de quaisquer observadores.", "simplex-unique-card-1-p-2": "Diferente de qualquer outra plataforma de mensagens existente, o SimpleX não tem identificadores atribuídos aos usuários — <strong>nem mesmo números aleatórios</strong>.", @@ -145,8 +145,8 @@ "join-us-on-GitHub": "Junte-se a nós no GitHub", "donate-here-to-help-us": "Doe aqui para nos ajudar", "sign-up-to-receive-our-updates": "Inscreva-se para receber nossas atualizações", - "enter-your-email-address": "Digite seu endereço de e-mail", - "get-simplex": "Obtenha o SimpleX <a href=\"/downloads\">desktop app</a>", + "enter-your-email-address": "Digite seu endereço de email", + "get-simplex": "Obtenha o SimpleX <a href=\"/downloads\">aplicativo desktop</a>", "why-simplex-is": "Por que o SimpleX é", "unique": "único", "learn-more": "Saiba mais", @@ -164,14 +164,14 @@ "guide-dropdown-2": "Enviando mensagens", "guide-dropdown-8": "Configurações do aplicativo", "guide-dropdown-9": "Fazendo conexões", - "docs-dropdown-4": "Servidor SMP de host", - "docs-dropdown-5": "Servidor XFTP de host", + "docs-dropdown-4": "Hospedar Servidor SMP", + "docs-dropdown-5": "Hospedar Servidor XFTP", "docs-dropdown-6": "Servidores WebRTC", - "docs-dropdown-7": "Traduzir o SimpleX Chat", + "docs-dropdown-7": "Traduza o SimpleX Chat", "use-this-command": "Use esse comando:", "github-repository": "Repositório no GitHub", "if-you-already-installed-simplex-chat-for-the-terminal": "Se você já instalou o SimpleX Chat para o terminal", - "copy-the-command-below-text": "copie o comando abaixo e use-o no chat:", + "copy-the-command-below-text": "copie o comando abaixo e use-o no bate-papo:", "privacy-matters-section-header": "Por que a privacidade é <span class='gradient-text'>importante</span>", "privacy-matters-section-label": "Certifique-se de que seu mensageiro não possa acessar seus dados!", "simplex-network-section-header": "<span class='gradient-text'>Rede</span> SimpleX", @@ -183,8 +183,8 @@ "simplex-network-1-header": "Diferente das redes P2P", "docs-dropdown-2": "Acessando arquivos do Android", "comparison-section-list-point-3": "Chave pública ou alguma outra ID globalmente exclusiva", - "comparison-section-list-point-4": "Se os servidores da operadora forem comprometidos", - "comparison-section-list-point-5": "Não protege os metadados dos usuários", + "comparison-section-list-point-4": "Se os servidores da operadora forem comprometidos. Verifique o código de segurança no Signal e outros aplicativos para mitigá-lo", + "comparison-section-list-point-5": "Não protege a privacidade da metadados dos usuários", "guide-dropdown-1": "Início rápido", "guide-dropdown-4": "Perfis de chat", "guide-dropdown-5": "Gerenciando dados", @@ -192,7 +192,7 @@ "guide-dropdown-7": "Privacidade e segurança", "guide": "Manual", "docs-dropdown-1": "Plataforma SimpleX", - "docs-dropdown-3": "Acesso ao banco de dados do chat", + "docs-dropdown-3": "Acessando o banco de dados do bate-papo", "on-this-page": "Nesta página", "newer-version-of-eng-msg": "Há uma versão mais recente desta página em inglês.", "click-to-see": "Clique para ver", @@ -232,5 +232,25 @@ "comparison-section-list-point-7": "As redes P2P têm uma autoridade central ou toda a rede pode ser comprometida", "protocol-2-text": "Matrix, XMPP", "see-here": "veja aqui", - "glossary": "Glossário" + "glossary": "Glossário", + "f-droid-page-simplex-chat-repo-section-text": "Para adicioná-lo ao seu cliente F-Droid, <span class='hide-on-mobile'>escaneie o QR code ou</span> use este URL:", + "comparison-section-list-point-4a": "Os relays SimpleX não podem comprometer a criptografia e2e. Verifique o código de segurança para mitigar ataques em canais fora de banda", + "please-use-link-in-mobile-app": "Use o link no aplicativo móvel", + "stable-and-beta-versions-built-by-developers": "Versões estáveis e beta criadas pelos desenvolvedores", + "signing-key-fingerprint": "Assinatura de impressão digital de chave (SHA-256)", + "simplex-chat-via-f-droid": "SimpleX Chat pelo F-Droid", + "simplex-chat-repo": "Repositório Simplex Chat", + "f-droid-org-repo": "Repositório F-Droid.org", + "stable-versions-built-by-f-droid-org": "Versões estáveis criadas por F-Droid.org", + "releases-to-this-repo-are-done-1-2-days-later": "Os lançamentos para este repositório são feitos 1-2 dias depois", + "hero-overlay-3-textlink": "Avaliação Segura", + "hero-overlay-3-title": "Avaliação Segura", + "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> é uma consultoria líder em segurança e tecnologia cujos clientes incluem grandes empresas de tecnologia, agências governamentais e grandes projetos de blockchain.", + "hero-overlay-card-3-p-2": "Trail of Bits analisou a criptografia da plataforma SimpleX e os componentes de rede em novembro de 2022.", + "hero-overlay-card-3-p-3": "Leia mais em <a href=\"/blog/20221108-simplex-chat-v4.2-security-audit-new-website.html\">o anúncio</a>.", + "f-droid-page-f-droid-org-repo-section-text": "Os repositórios SimpleX Chat e F-Droid.org assinam compilações com chaves diferentes. Para mudar, <a href='/docs/guide/chat-profiles.html#move-your-chat-profiles-to-another-device'>exporte</a> o banco de dados de bate-papo e reinstale o aplicativo.", + "please-enable-javascript": "Por favor habilite o JavaScript para ver o QR code.", + "jobs": "Junte-se à equipe", + "docs-dropdown-8": "Serviço de Diretório SimpleX", + "docs-dropdown-9": "Baixar" } diff --git a/website/langs/uk.json b/website/langs/uk.json index e71b7cef8d..6289b44ca8 100644 --- a/website/langs/uk.json +++ b/website/langs/uk.json @@ -250,5 +250,7 @@ "hero-overlay-3-textlink": "Оцінка безпеки", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> є провідною консалтинговою фірмою з безпеки та технологій, клієнтами якої є великі технологічні компанії, урядові агенції та великі проекти у сфері блокчейну.", "comparison-section-list-point-4a": "Ретранслятори SimpleX не можуть порушити e2e-шифрування. Перевірте безпековий код для зменшення ризику атаки на зовнішньобандовий канал", - "docs-dropdown-9": "Завантаження" -} \ No newline at end of file + "docs-dropdown-9": "Завантаження", + "please-enable-javascript": "Будь ласка, увімкніть JavaScript, щоб побачити QR-код.", + "please-use-link-in-mobile-app": "Будь ласка, скористайтеся посиланням у мобільному додатку" +} diff --git a/website/langs/zh_Hans.json b/website/langs/zh_Hans.json index e787bced83..ded7c7e19b 100644 --- a/website/langs/zh_Hans.json +++ b/website/langs/zh_Hans.json @@ -250,5 +250,7 @@ "jobs": "加入团队", "hero-overlay-3-textlink": "安全性评估", "hero-overlay-card-3-p-1": "<a href=\"https://www.trailofbits.com/about/\">Trail of Bits</a> 是一家领先的安全和技术咨询企业,其客户包括大型科技公司、政府机构和重要的区块链项目。", - "docs-dropdown-9": "下载" + "docs-dropdown-9": "下载", + "please-use-link-in-mobile-app": "请使用移动应用程序中的链接", + "please-enable-javascript": "请启用 JavaScript 以查看二维码。" } diff --git a/website/src/_includes/blog_previews/20240314.html b/website/src/_includes/blog_previews/20240314.html new file mode 100644 index 0000000000..8a9ac949a7 --- /dev/null +++ b/website/src/_includes/blog_previews/20240314.html @@ -0,0 +1,12 @@ +<p class="mb-[12px]">This is a major upgrade for SimpleX Chat messaging protocols!</p> + +<p class="mb-[12px]">This post also covers various aspects of end-to-end encryption:</p> + +<ul> + <li>Why do we need end-to-end encryption?</li> + <li>Why encryption is even allowed?</li> + <li>End-to-end encryption security: attacks and defence.</li> + <li>How secure is encryption in different messengers?</li> + <li>When can you start using quantum resistant chats?</li> + <li>Next for post-quantum crypto - all direct chats, small groups and security audit</li> +</ul> diff --git a/website/src/css/blog.css b/website/src/css/blog.css index adec9ab04e..f3517958e2 100644 --- a/website/src/css/blog.css +++ b/website/src/css/blog.css @@ -210,4 +210,9 @@ h6{ float: left; margin-right: 3rem; } + + #article .float-to-right{ + float: right; + margin-left: 3rem; + } } \ No newline at end of file diff --git a/website/src/finneyforum.html b/website/src/finneyforum.html new file mode 100644 index 0000000000..06229e4b5d --- /dev/null +++ b/website/src/finneyforum.html @@ -0,0 +1,8 @@ +--- +layout: layouts/group_link.html +title: "SimpleX Chat - Finney Forum group" +description: "Join the group of attendees of Finney Forum 2024" +groupLink: "https://simplex.chat/contact#/?v=1-4&smp=smp%3A%2F%2Fhpq7_4gGJiilmz5Rf-CswuU5kZGkm_zOIooSw6yALRg%3D%40smp5.simplex.im%2FTlom_0qzRaEWo_4cweE_hzj6KBmqXC8R%23%2F%3Fv%3D1-2%26dh%3DMCowBQYDK2VuAyEAZzyx3sm1tpGsYjXAOR2LxXD0ty1hlAR7Hg0fbCxEoig%253D%26srv%3Djjbyvoemxysm7qxap7m5d5m35jzv5qq6gnlv7s4rsn7tdwwmuqciwpid.onion&data=%7B%22type%22%3A%22group%22%2C%22groupLinkId%22%3A%22IfdftVGf9odVOQImmz1I9A%3D%3D%22%7D" +groupLinkText: Open Finney Forum group link +templateEngineOverride: njk +---