diff --git a/apps/ios/Shared/Model/SimpleXAPI.swift b/apps/ios/Shared/Model/SimpleXAPI.swift index abd50e61b1..c5953b4c78 100644 --- a/apps/ios/Shared/Model/SimpleXAPI.swift +++ b/apps/ios/Shared/Model/SimpleXAPI.swift @@ -561,6 +561,18 @@ func apiGroupMemberInfo(_ groupId: Int64, _ groupMemberId: Int64) throws -> (Gro throw r } +func apiContactQueueInfo(_ contactId: Int64) async throws -> (RcvMsgInfo?, QueueInfo) { + let r = await chatSendCmd(.apiContactQueueInfo(contactId: contactId)) + if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } + throw r +} + +func apiGroupMemberQueueInfo(_ groupId: Int64, _ groupMemberId: Int64) async throws -> (RcvMsgInfo?, QueueInfo) { + let r = await chatSendCmd(.apiGroupMemberQueueInfo(groupId: groupId, groupMemberId: groupMemberId)) + if case let .queueInfo(_, rcvMsgInfo, queueInfo) = r { return (rcvMsgInfo, queueInfo) } + throw r +} + func apiSwitchContact(contactId: Int64) throws -> ConnectionStats { let r = chatSendCmdSync(.apiSwitchContact(contactId: contactId)) if case let .contactSwitchStarted(_, _, connectionStats) = r { return connectionStats } @@ -1314,7 +1326,7 @@ func filterMembersToAdd(_ ms: [GMember]) -> [Contact] { let memberContactIds = ms.compactMap{ m in m.wrapped.memberCurrent ? m.wrapped.memberContactId : nil } return ChatModel.shared.chats .compactMap{ $0.chatInfo.contact } - .filter{ c in c.ready && c.active && !memberContactIds.contains(c.apiId) } + .filter{ c in c.sendMsgEnabled && !c.nextSendGrpInv && !memberContactIds.contains(c.apiId) } .sorted{ $0.displayName.lowercased() < $1.displayName.lowercased() } } @@ -1889,7 +1901,7 @@ func processReceivedMsg(_ res: ChatResponse) async { } case let .sndFileCompleteXFTP(user, aChatItem, _): await chatItemSimpleUpdate(user, aChatItem) - case let .sndFileError(user, aChatItem, _): + case let .sndFileError(user, aChatItem, _, _): if let aChatItem = aChatItem { await chatItemSimpleUpdate(user, aChatItem) Task { cleanupFile(aChatItem) } diff --git a/apps/ios/Shared/Views/Chat/ChatInfoView.swift b/apps/ios/Shared/Views/Chat/ChatInfoView.swift index 5143c67c3c..0f112a24ee 100644 --- a/apps/ios/Shared/Views/Chat/ChatInfoView.swift +++ b/apps/ios/Shared/Views/Chat/ChatInfoView.swift @@ -126,6 +126,7 @@ struct ChatInfoView: View { case abortSwitchAddressAlert case syncConnectionForceAlert case deleteContactNotice + case queueInfo(info: String) case error(title: LocalizedStringKey, error: LocalizedStringKey = "") var id: String { @@ -136,6 +137,7 @@ struct ChatInfoView: View { case .abortSwitchAddressAlert: return "abortSwitchAddressAlert" case .syncConnectionForceAlert: return "syncConnectionForceAlert" case .deleteContactNotice: return "deleteContactNotice" + case let .queueInfo(info): return "queueInfo \(info)" case let .error(title, _): return "error \(title)" } } @@ -270,6 +272,18 @@ struct ChatInfoView: View { Section(header: Text("For console")) { infoRow("Local name", chat.chatInfo.localDisplayName) infoRow("Database ID", "\(chat.chatInfo.apiId)") + Button ("Debug delivery") { + Task { + do { + let info = queueInfoText(try await apiContactQueueInfo(chat.chatInfo.apiId)) + await MainActor.run { alert = .queueInfo(info: info) } + } catch let e { + logger.error("apiContactQueueInfo error: \(responseError(e))") + let a = getErrorAlert(e, "Error") + await MainActor.run { alert = .error(title: a.title, error: a.message) } + } + } + } } } } @@ -307,6 +321,7 @@ struct ChatInfoView: View { case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchContactAddress) case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncContactConnection(force: true) }) case .deleteContactNotice: return deleteContactNotice(contact) + case let .queueInfo(info): return queueInfoAlert(info) case let .error(title, error): return mkAlert(title: title, message: error) } } @@ -785,6 +800,22 @@ func syncConnectionForceAlert(_ syncConnectionForce: @escaping () -> Void) -> Al ) } +func queueInfoText(_ info: (RcvMsgInfo?, QueueInfo)) -> String { + let (rcvMsgInfo, qInfo) = info + var msgInfo: String + if let rcvMsgInfo { msgInfo = encodeJSON(rcvMsgInfo) } else { msgInfo = "none" } + return String.localizedStringWithFormat(NSLocalizedString("server queue info: %@\n\nlast received msg: %@", comment: "queue info"), encodeJSON(qInfo), msgInfo) +} + +func queueInfoAlert(_ info: String) -> Alert { + Alert( + title: Text("Message queue info"), + message: Text(info), + primaryButton: .default(Text("Ok")), + secondaryButton: .default(Text("Copy")) { UIPasteboard.general.string = info } + ) +} + struct ChatInfoView_Previews: PreviewProvider { static var previews: some View { ChatInfoView( diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift index 0af0469e42..53d840306e 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIFileView.swift @@ -54,7 +54,7 @@ struct CIFileView: View { switch (file.fileStatus) { case .sndStored: return file.fileProtocol == .local case .sndTransfer: return false - case .sndComplete: return false + case .sndComplete: return true case .sndCancelled: return false case .sndError: return false case .rcvInvitation: return true @@ -113,6 +113,11 @@ struct CIFileView: View { if file.fileProtocol == .local, let fileSource = getLoadedFileSource(file) { saveCryptoFile(fileSource) } + case .sndComplete: + logger.debug("CIFileView fileAction - in .sndComplete") + if let fileSource = getLoadedFileSource(file) { + saveCryptoFile(fileSource) + } default: break } } diff --git a/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift b/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift index 0299a5e6f8..40ed8bc76c 100644 --- a/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift +++ b/apps/ios/Shared/Views/Chat/ChatItem/CIInvalidJSONView.swift @@ -24,7 +24,7 @@ struct CIInvalidJSONView: View { .cornerRadius(18) .textSelection(.disabled) .onTapGesture { showJSON = true } - .sheet(isPresented: $showJSON) { + .appSheet(isPresented: $showJSON) { invalidJSONView(json) } } diff --git a/apps/ios/Shared/Views/Chat/ChatView.swift b/apps/ios/Shared/Views/Chat/ChatView.swift index aafafbbc80..eb1b5d5e41 100644 --- a/apps/ios/Shared/Views/Chat/ChatView.swift +++ b/apps/ios/Shared/Views/Chat/ChatView.swift @@ -113,7 +113,7 @@ struct ChatView: View { } label: { ChatInfoToolbar(chat: chat) } - .sheet(isPresented: $showChatInfoSheet) { + .appSheet(isPresented: $showChatInfoSheet) { ChatInfoView( openedFromChatView: true, chat: chat, diff --git a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift index c5e1ccd788..5199c81073 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupMemberInfoView.swift @@ -35,6 +35,7 @@ struct GroupMemberInfoView: View { case abortSwitchAddressAlert case syncConnectionForceAlert case planAndConnectAlert(alert: PlanAndConnectAlert) + case queueInfo(info: String) case error(title: LocalizedStringKey, error: LocalizedStringKey) var id: String { @@ -49,6 +50,7 @@ struct GroupMemberInfoView: View { case .abortSwitchAddressAlert: return "abortSwitchAddressAlert" case .syncConnectionForceAlert: return "syncConnectionForceAlert" case let .planAndConnectAlert(alert): return "planAndConnectAlert \(alert.id)" + case let .queueInfo(info): return "queueInfo \(info)" case let .error(title, _): return "error \(title)" } } @@ -196,6 +198,18 @@ struct GroupMemberInfoView: View { Section("For console") { infoRow("Local name", member.localDisplayName) infoRow("Database ID", "\(member.groupMemberId)") + Button ("Debug delivery") { + Task { + do { + let info = queueInfoText(try await apiGroupMemberQueueInfo(groupInfo.apiId, member.groupMemberId)) + await MainActor.run { alert = .queueInfo(info: info) } + } catch let e { + logger.error("apiContactQueueInfo error: \(responseError(e))") + let a = getErrorAlert(e, "Error") + await MainActor.run { alert = .error(title: a.title, error: a.message) } + } + } + } } } } @@ -241,6 +255,7 @@ struct GroupMemberInfoView: View { case .abortSwitchAddressAlert: return abortSwitchAddressAlert(abortSwitchMemberAddress) case .syncConnectionForceAlert: return syncConnectionForceAlert({ syncMemberConnection(force: true) }) case let .planAndConnectAlert(alert): return planAndConnectAlert(alert, dismiss: true) + case let .queueInfo(info): return queueInfoAlert(info) case let .error(title, error): return Alert(title: Text(title), message: Text(error)) } } diff --git a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift index b4e1992848..6ae5032be5 100644 --- a/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift +++ b/apps/ios/Shared/Views/Chat/Group/GroupPreferencesView.swift @@ -34,8 +34,7 @@ struct GroupPreferencesView: View { featureSection(.reactions, $preferences.reactions.enable) featureSection(.voice, $preferences.voice.enable, $preferences.voice.role) featureSection(.files, $preferences.files.enable, $preferences.files.role) - // TODO enable simplexLinks preference in 5.8 - // featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role) + featureSection(.simplexLinks, $preferences.simplexLinks.enable, $preferences.simplexLinks.role) featureSection(.history, $preferences.history.enable) if groupInfo.canEdit { @@ -102,8 +101,6 @@ struct GroupPreferencesView: View { } } .frame(height: 36) - // remove in v5.8 - .disabled(true) } } else { settingsRow(icon, color: color) { diff --git a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift index 5086595e73..0e894b7fa0 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListNavLink.swift @@ -392,10 +392,12 @@ struct ChatListNavLink: View { .tint(.accentColor) } .frame(height: rowHeights[dynamicTypeSize]) - .sheet(isPresented: $showContactConnectionInfo) { - if case let .contactConnection(contactConnection) = chat.chatInfo { - ContactConnectionInfo(contactConnection: contactConnection) - .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) + .appSheet(isPresented: $showContactConnectionInfo) { + Group { + if case let .contactConnection(contactConnection) = chat.chatInfo { + ContactConnectionInfo(contactConnection: contactConnection) + .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) + } } } .onTapGesture { @@ -521,7 +523,7 @@ struct ChatListNavLink: View { .padding(4) .frame(height: rowHeights[dynamicTypeSize]) .onTapGesture { showInvalidJSON = true } - .sheet(isPresented: $showInvalidJSON) { + .appSheet(isPresented: $showInvalidJSON) { invalidJSONView(json) .environment(\EnvironmentValues.refresh as! WritableKeyPath, nil) } diff --git a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift index 209c440b16..86e14b74f7 100644 --- a/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift +++ b/apps/ios/Shared/Views/Onboarding/WhatsNewView.swift @@ -406,7 +406,28 @@ private let versionDescriptions: [VersionDescription] = [ description: "More reliable network connection." ) ] - ) + ), + VersionDescription( + version: "v5.8", + post: URL(string: "https://simplex.chat/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.html"), + features: [ + FeatureDescription( + icon: "arrow.forward", + title: "Private message routing 🚀", + description: "Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." + ), + FeatureDescription( + icon: "network.badge.shield.half.filled", + title: "Safely receive files", + description: "Confirm files from unknown servers." + ), + FeatureDescription( + icon: "battery.50", + title: "Improved message delivery", + description: "With reduced battery usage." + ) + ] + ), ] private let lastVersion = versionDescriptions.last!.version diff --git a/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift b/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift index 54a4ef1489..6d849479e5 100644 --- a/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift +++ b/apps/ios/Shared/Views/UserSettings/NetworkAndServers.swift @@ -82,29 +82,29 @@ struct NetworkAndServers: View { Text("Using .onion hosts requires compatible VPN provider.") } -// Section { -// Picker("Private routing", selection: $proxyMode) { -// ForEach(SMPProxyMode.values, id: \.self) { Text($0.text) } -// } -// .frame(height: 36) -// -// Picker("Allow downgrade", selection: $proxyFallback) { -// ForEach(SMPProxyFallback.values, id: \.self) { Text($0.text) } -// } -// .disabled(proxyMode == .never) -// .frame(height: 36) -// -// Toggle("Show message status", isOn: $showSentViaProxy) -// } header: { -// Text("Private message routing") -// } footer: { -// VStack(alignment: .leading) { -// Text("To protect your IP address, private routing uses your SMP servers to deliver messages.") -// if showSentViaProxy { -// Text("Show → on messages sent via private routing.") -// } -// } -// } + Section { + Picker("Private routing", selection: $proxyMode) { + ForEach(SMPProxyMode.values, id: \.self) { Text($0.text) } + } + .frame(height: 36) + + Picker("Allow downgrade", selection: $proxyFallback) { + ForEach(SMPProxyFallback.values, id: \.self) { Text($0.text) } + } + .disabled(proxyMode == .never) + .frame(height: 36) + + Toggle("Show message status", isOn: $showSentViaProxy) + } header: { + Text("Private message routing") + } footer: { + VStack(alignment: .leading) { + Text("To protect your IP address, private routing uses your SMP servers to deliver messages.") + if showSentViaProxy { + Text("Show → on messages sent via private routing.") + } + } + } Section("Calls") { NavigationLink { diff --git a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift index 15d877d425..64356d4589 100644 --- a/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift +++ b/apps/ios/Shared/Views/UserSettings/UserProfilesView.swift @@ -123,7 +123,7 @@ struct UserProfilesView: View { deleteModeButton("Profile and server connections", true) deleteModeButton("Local profile data only", false) } - .sheet(item: $selectedUser) { user in + .appSheet(item: $selectedUser) { user in HiddenProfileView(user: user, profileHidden: $profileHidden) } .onChange(of: profileHidden) { _ in @@ -131,7 +131,7 @@ struct UserProfilesView: View { withAnimation { profileHidden = false } } } - .sheet(item: $profileAction) { action in + .appSheet(item: $profileAction) { action in profileActionView(action) } .alert(item: $alert) { alert in 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 3d0bb2bf2c..673d6668f9 100644 --- a/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff +++ b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff @@ -713,6 +713,10 @@ Позволи изчезващи съобщения само ако вашият контакт ги разрешава. No comment provided by engineer. + + Allow downgrade + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Позволи необратимо изтриване на съобщение само ако вашият контакт го рарешава. (24 часа) @@ -808,6 +812,10 @@ Вече се присъединихте към групата! No comment provided by engineer. + + Always use private routing. + No comment provided by engineer. + Always use relay Винаги използвай реле @@ -1088,6 +1096,10 @@ Файлът не може да бъде получен No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + snd error text + Cellular Мобилна мрежа @@ -1284,6 +1296,10 @@ Потвърди актуализаациите на базата данни No comment provided by engineer. + + Confirm files from unknown servers. + No comment provided by engineer. + Confirm network settings Потвърди мрежовите настройки @@ -1701,6 +1717,10 @@ This is your own one-time link! Базата данни ще бъде мигрирана, когато приложението се рестартира No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized Децентрализиран @@ -1948,6 +1968,10 @@ This cannot be undone! Настолни устройства No comment provided by engineer. + + Destination server error: %@ + snd error text + Develop Разработване @@ -2053,11 +2077,19 @@ This cannot be undone! Откриване през локалната мрежа No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. НЕ използвайте SimpleX за спешни повиквания. No comment provided by engineer. + + Do NOT use private routing. + No comment provided by engineer. + Do it later Отложи @@ -2621,7 +2653,7 @@ This cannot be undone! Error: %@ Грешка: %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2713,6 +2745,10 @@ This cannot be undone! Файл: %@ No comment provided by engineer. + + Files + No comment provided by engineer. + Files & media Файлове и медия @@ -2818,6 +2854,16 @@ This cannot be undone! Препратено от No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + snd error text + Found desktop Намерено настолно устройство @@ -3633,11 +3679,19 @@ This is your link for group %@! Потвърждениe за доставка на съобщения! No comment provided by engineer. + + Message delivery warning + item status text + Message draft Чернова на съобщение No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions Реакции на съобщения @@ -3653,6 +3707,14 @@ This is your link for group %@! Реакциите на съобщения са забранени в тази група. No comment provided by engineer. + + Message routing fallback + No comment provided by engineer. + + + Message routing mode + No comment provided by engineer. + Message source remains private. Източникът на съобщението остава скрит. @@ -3783,11 +3845,6 @@ This is your link for group %@! Най-вероятно тази връзка е изтрита. item status description - - Most likely this contact has deleted the connection with you. - Най-вероятно този контакт е изтрил връзката с вас. - No comment provided by engineer. - Multiple chat profiles Множество профили за чат @@ -3818,6 +3875,10 @@ This is your link for group %@! Мрежова връзка No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + snd error text + Network management Управление на мрежата @@ -4359,11 +4420,23 @@ Error: %@ Поверителни имена на файлове No comment provided by engineer. + + Private message routing + No comment provided by engineer. + + + Private message routing 🚀 + No comment provided by engineer. + Private notes Лични бележки name of notes to self + + Private routing + No comment provided by engineer. + Profile and server connections Профилни и сървърни връзки @@ -4444,11 +4517,20 @@ Error: %@ Забрани изпращането на гласови съобщения. No comment provided by engineer. + + Protect IP address + No comment provided by engineer. + Protect app screen Защити екрана на приложението No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + No comment provided by engineer. + Protect your chat profiles with a password! Защитете чат профилите с парола! @@ -4554,11 +4636,6 @@ Error: %@ Получаващият адрес ще бъде променен към друг сървър. Промяната на адреса ще завърши, след като подателят е онлайн. No comment provided by engineer. - - Receiving concurrency - Паралелност на получаване - No comment provided by engineer. - Receiving file will be stopped. Получаващият се файл ще бъде спрян. @@ -4794,6 +4871,10 @@ Error: %@ SMP сървъри No comment provided by engineer. + + Safely receive files + No comment provided by engineer. + Safer groups По-безопасни групи @@ -5019,6 +5100,14 @@ Error: %@ Изпрати съобщение на живо No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + No comment provided by engineer. + Send notifications Изпращай известия @@ -5124,6 +5213,10 @@ Error: %@ Изпратените съобщения ще бъдат изтрити след зададеното време. No comment provided by engineer. + + Server address is incompatible with network settings. + srv error text. + Server requires authorization to create queues, check password Сървърът изисква оторизация за създаване на опашки, проверете паролата @@ -5139,6 +5232,10 @@ Error: %@ Тестът на сървъра е неуспешен! No comment provided by engineer. + + Server version is incompatible with network settings. + srv error text + Servers Сървъри @@ -5259,11 +5356,19 @@ Error: %@ Показване на последните съобщения в листа с чатовете No comment provided by engineer. + + Show message status + No comment provided by engineer. + Show preview Показване на визуализация No comment provided by engineer. + + Show → on messages sent via private routing. + No comment provided by engineer. + Show: Покажи: @@ -5586,6 +5691,10 @@ It can happen because of some bug or when the connection is compromised.Приложението може да ви уведоми, когато получите съобщения или заявки за контакт - моля, отворете настройките, за да активирате. No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + No comment provided by engineer. + The attempt to change database passphrase was not completed. Опитът за промяна на паролата на базата данни не беше завършен. @@ -5771,6 +5880,10 @@ It can happen because of some bug or when the connection is compromised.За да не се разкрива часовата зона, файловете с изображения/глас използват UTC. No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + 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. @@ -5863,11 +5976,6 @@ You will be prompted to complete authentication before this feature is enabled.< Отблокирай член? No comment provided by engineer. - - Unexpected error: %@ - Неочаквана грешка: %@ - item status description - Unexpected migration state Неочаквано състояние на миграция @@ -5913,6 +6021,10 @@ You will be prompted to complete authentication before this feature is enabled.< Непозната грешка No comment provided by engineer. + + Unknown servers! + No comment provided by engineer. + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. Освен ако не използвате интерфейса за повикване на iOS, активирайте режима "Не безпокой", за да избегнете прекъсвания. @@ -6060,6 +6172,14 @@ To connect, please ask your contact to create another connection link and check Използвай само локални известия? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + No comment provided by engineer. + + + Use private routing with unknown servers. + No comment provided by engineer. + Use server Използвай сървър @@ -6295,11 +6415,23 @@ To connect, please ask your contact to create another connection link and check С намален разход на батерията. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + No comment provided by engineer. + Wrong database passphrase Грешна парола за базата данни No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + snd error text + Wrong passphrase! Грешна парола! @@ -7415,6 +7547,12 @@ SimpleX сървърите не могат да видят вашия профи изпрати лично съобщение No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address зададен нов адрес за контакт @@ -7455,11 +7593,19 @@ SimpleX сървърите не могат да видят вашия профи неизвестен connection info + + unknown relays + No comment provided by engineer. + unknown status неизвестен статус No comment provided by engineer. + + unprotected + No comment provided by engineer. + updated group profile актуализиран профил на групата @@ -7525,6 +7671,10 @@ SimpleX сървърите не могат да видят вашия профи седмици time unit + + when IP hidden + No comment provided by engineer. + yes да 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 a1ef39b2fb..24c71a3487 100644 --- a/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff +++ b/apps/ios/SimpleX Localizations/cs.xcloc/Localized Contents/cs.xliff @@ -690,6 +690,10 @@ Povolte mizící zprávy, pouze pokud vám to váš kontakt dovolí. No comment provided by engineer. + + Allow downgrade + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Povolte nevratné smazání zprávy pouze v případě, že vám to váš kontakt dovolí. (24 hodin) @@ -782,6 +786,10 @@ Already joining the group! No comment provided by engineer. + + Always use private routing. + No comment provided by engineer. + Always use relay Spojení přes relé @@ -1047,6 +1055,10 @@ Nelze přijmout soubor No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + snd error text + Cellular No comment provided by engineer. @@ -1238,6 +1250,10 @@ Potvrdit aktualizaci databáze No comment provided by engineer. + + Confirm files from unknown servers. + No comment provided by engineer. + Confirm network settings No comment provided by engineer. @@ -1631,6 +1647,10 @@ This is your own one-time link! Databáze bude přenesena po restartu aplikace No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized Decentralizované @@ -1870,6 +1890,10 @@ This cannot be undone! Desktop devices No comment provided by engineer. + + Destination server error: %@ + snd error text + Develop Vyvinout @@ -1973,11 +1997,19 @@ This cannot be undone! Discover via local network No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. NEpoužívejte SimpleX pro tísňová volání. No comment provided by engineer. + + Do NOT use private routing. + No comment provided by engineer. + Do it later Udělat později @@ -2519,7 +2551,7 @@ This cannot be undone! Error: %@ Chyba: %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2608,6 +2640,10 @@ This cannot be undone! Soubor: %@ No comment provided by engineer. + + Files + No comment provided by engineer. + Files & media Soubory a média @@ -2706,6 +2742,16 @@ This cannot be undone! Forwarded from No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + snd error text + Found desktop No comment provided by engineer. @@ -3490,11 +3536,19 @@ This is your link for group %@! Potvrzení o doručení zprávy! No comment provided by engineer. + + Message delivery warning + item status text + Message draft Návrh zprávy No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions Reakce na zprávy @@ -3510,6 +3564,14 @@ This is your link for group %@! Reakce na zprávy jsou v této skupině zakázány. No comment provided by engineer. + + Message routing fallback + No comment provided by engineer. + + + Message routing mode + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -3627,11 +3689,6 @@ This is your link for group %@! Pravděpodobně je toto spojení smazáno. item status description - - Most likely this contact has deleted the connection with you. - Tento kontakt s největší pravděpodobností smazal spojení s vámi. - No comment provided by engineer. - Multiple chat profiles Více chatovacích profilů @@ -3661,6 +3718,10 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + snd error text + Network management No comment provided by engineer. @@ -4181,10 +4242,22 @@ Error: %@ Soukromé názvy souborů No comment provided by engineer. + + Private message routing + No comment provided by engineer. + + + Private message routing 🚀 + No comment provided by engineer. + Private notes name of notes to self + + Private routing + No comment provided by engineer. + Profile and server connections Profil a připojení k serveru @@ -4261,11 +4334,20 @@ Error: %@ Zakázat odesílání hlasových zpráv. No comment provided by engineer. + + Protect IP address + No comment provided by engineer. + Protect app screen Ochrana obrazovky aplikace No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + No comment provided by engineer. + Protect your chat profiles with a password! Chraňte své chat profily heslem! @@ -4368,10 +4450,6 @@ Error: %@ Přijímací adresa bude změněna na jiný server. Změna adresy bude dokončena po připojení odesílatele. No comment provided by engineer. - - Receiving concurrency - No comment provided by engineer. - Receiving file will be stopped. Příjem souboru bude zastaven. @@ -4599,6 +4677,10 @@ Error: %@ SMP servery No comment provided by engineer. + + Safely receive files + No comment provided by engineer. + Safer groups No comment provided by engineer. @@ -4817,6 +4899,14 @@ Error: %@ Odeslat živou zprávu No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + No comment provided by engineer. + Send notifications Odeslat oznámení @@ -4921,6 +5011,10 @@ Error: %@ Odeslané zprávy se po uplynutí nastavené doby odstraní. No comment provided by engineer. + + Server address is incompatible with network settings. + srv error text. + Server requires authorization to create queues, check password Server vyžaduje autorizaci pro vytváření front, zkontrolujte heslo @@ -4936,6 +5030,10 @@ Error: %@ Test serveru se nezdařil! No comment provided by engineer. + + Server version is incompatible with network settings. + srv error text + Servers Servery @@ -5051,11 +5149,19 @@ Error: %@ Zobrazit poslední zprávy No comment provided by engineer. + + Show message status + No comment provided by engineer. + Show preview Zobrazení náhledu No comment provided by engineer. + + Show → on messages sent via private routing. + No comment provided by engineer. + Show: Zobrazit: @@ -5369,6 +5475,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován Aplikace vás může upozornit na přijaté zprávy nebo žádosti o kontakt - povolte to v nastavení. No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + No comment provided by engineer. + The attempt to change database passphrase was not completed. Pokus o změnu přístupové fráze databáze nebyl dokončen. @@ -5545,6 +5655,10 @@ Může se to stát kvůli nějaké chybě, nebo pokud je spojení kompromitován K ochraně časového pásma používají obrazové/hlasové soubory UTC. No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + 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. @@ -5631,11 +5745,6 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření. Unblock member? No comment provided by engineer. - - Unexpected error: %@ - Neočekávaná chyba: %@ - item status description - Unexpected migration state Neočekávaný stav přenášení @@ -5681,6 +5790,10 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření. Neznámá chyba No comment provided by engineer. + + Unknown servers! + No comment provided by engineer. + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. Při nepoužívání rozhraní volání iOS, povolte režim Nerušit, abyste se vyhnuli vyrušování. @@ -5821,6 +5934,14 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu Use only local notifications? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + No comment provided by engineer. + + + Use private routing with unknown servers. + No comment provided by engineer. + Use server Použít server @@ -6038,11 +6159,23 @@ Chcete-li se připojit, požádejte svůj kontakt o vytvoření dalšího odkazu With reduced battery usage. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + No comment provided by engineer. + Wrong database passphrase Špatná přístupová fráze k databázi No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + snd error text + Wrong passphrase! Špatná přístupová fráze! @@ -7122,6 +7255,12 @@ Servery SimpleX nevidí váš profil. odeslat přímou zprávu No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address profile update event chat item @@ -7158,10 +7297,18 @@ Servery SimpleX nevidí váš profil. neznámý connection info + + unknown relays + No comment provided by engineer. + unknown status No comment provided by engineer. + + unprotected + No comment provided by engineer. + updated group profile aktualizoval profil skupiny @@ -7225,6 +7372,10 @@ Servery SimpleX nevidí váš profil. týdnů time unit + + when IP hidden + No comment provided by engineer. + yes ano 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 29384aecc4..4889950d33 100644 --- a/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff +++ b/apps/ios/SimpleX Localizations/de.xcloc/Localized Contents/de.xliff @@ -695,7 +695,7 @@ 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. + Alle Ihre Kontakte, Unterhaltungen und Dateien werden sicher verschlüsselt und in Daten-Paketen auf die konfigurierten XTFP-Relais hochgeladen. No comment provided by engineer. @@ -713,6 +713,11 @@ Erlauben Sie verschwindende Nachrichten nur dann, wenn es Ihr Kontakt ebenfalls erlaubt. No comment provided by engineer. + + Allow downgrade + Herabstufung erlauben + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Erlauben Sie das unwiederbringliche Löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt. (24 Stunden) @@ -808,6 +813,11 @@ Sie sind bereits Mitglied der Gruppe! No comment provided by engineer. + + Always use private routing. + Sie nutzen immer privates Routing. + No comment provided by engineer. + Always use relay Über ein Relais verbinden @@ -1088,6 +1098,11 @@ Datei kann nicht empfangen werden No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + Kapazität überschritten - der Empfänger hat die zuvor gesendeten Nachrichten nicht empfangen. + snd error text + Cellular Zellulär @@ -1284,6 +1299,11 @@ Datenbank-Aktualisierungen bestätigen No comment provided by engineer. + + Confirm files from unknown servers. + Dateien von unbekannten Servern bestätigen. + No comment provided by engineer. + Confirm network settings Bestätigen Sie die Netzwerkeinstellungen @@ -1701,6 +1721,10 @@ Das ist Ihr eigener Einmal-Link! Die Datenbank wird beim nächsten Start der App migriert No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized Dezentral @@ -1948,6 +1972,11 @@ Das kann nicht rückgängig gemacht werden! Desktop-Geräte No comment provided by engineer. + + Destination server error: %@ + Zielserver-Fehler: %@ + snd error text + Develop Entwicklung @@ -2053,9 +2082,19 @@ Das kann nicht rückgängig gemacht werden! Lokales Netzwerk durchsuchen No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + Nachrichten werden nicht direkt versendet, selbst wenn Ihr oder der Zielserver kein privates Routing unterstützt. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. - Nutzen Sie SimpleX nicht für Notrufe. + SimpleX NICHT für Notrufe nutzen. + No comment provided by engineer. + + + Do NOT use private routing. + Sie nutzen KEIN privates Routing. No comment provided by engineer. @@ -2621,7 +2660,7 @@ Das kann nicht rückgängig gemacht werden! Error: %@ Fehler: %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2713,6 +2752,11 @@ Das kann nicht rückgängig gemacht werden! Datei: %@ No comment provided by engineer. + + Files + Dateien + No comment provided by engineer. + Files & media Dateien & Medien @@ -2818,6 +2862,20 @@ Das kann nicht rückgängig gemacht werden! Weitergeleitet aus No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + Weiterleitungsserver: %1$@ +Zielserver Fehler: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + Weiterleitungsserver: %1$@ +Fehler: %2$@ + snd error text + Found desktop Gefundener Desktop @@ -3633,11 +3691,20 @@ Das ist Ihr Link für die Gruppe %@! Empfangsbestätigungen für Nachrichten! No comment provided by engineer. + + Message delivery warning + Warnung bei der Nachrichtenzustellung + item status text + Message draft Nachrichtenentwurf No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions Reaktionen auf Nachrichten @@ -3653,6 +3720,16 @@ Das ist Ihr Link für die Gruppe %@! In dieser Gruppe sind Reaktionen auf Nachrichten nicht erlaubt. No comment provided by engineer. + + Message routing fallback + Fallback für das Nachrichten-Routing + No comment provided by engineer. + + + Message routing mode + Modus für das Nachrichten-Routing + No comment provided by engineer. + Message source remains private. Die Nachrichtenquelle bleibt privat. @@ -3783,11 +3860,6 @@ Das ist Ihr Link für die Gruppe %@! Wahrscheinlich ist diese Verbindung gelöscht worden. item status description - - Most likely this contact has deleted the connection with you. - Dieser Kontakt hat sehr wahrscheinlich die Verbindung mit Ihnen gelöscht. - No comment provided by engineer. - Multiple chat profiles Mehrere Chat-Profile @@ -3818,6 +3890,11 @@ Das ist Ihr Link für die Gruppe %@! Netzwerkverbindung No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + Netzwerk-Fehler - die Nachricht ist nach vielen Sende-Versuchen abgelaufen. + snd error text + Network management Netzwerk-Verwaltung @@ -4004,12 +4081,12 @@ Das ist Ihr Link für die Gruppe %@! Onion hosts will be required for connection. Requires enabling VPN. - Für die Verbindung werden Onion-Hosts benötigt. Dies erfordert die Aktivierung eines VPNs. + Für diese Verbindung werden Onion-Hosts benötigt. Dies erfordert die Aktivierung eines VPNs. No comment provided by engineer. Onion hosts will be used when available. Requires enabling VPN. - Onion-Hosts werden verwendet, sobald sie verfügbar sind. Dies erfordert die Aktivierung eines VPNs. + Wenn Onion-Hosts verfügbar sind, werden sie verwendet. Dies erfordert die Aktivierung eines VPNs. No comment provided by engineer. @@ -4359,11 +4436,26 @@ Fehler: %@ Neutrale Dateinamen No comment provided by engineer. + + Private message routing + Privates Nachrichten-Routing + No comment provided by engineer. + + + Private message routing 🚀 + Privates Nachrichten-Routing 🚀 + No comment provided by engineer. + Private notes Private Notizen name of notes to self + + Private routing + Privates Routing + No comment provided by engineer. + Profile and server connections Profil und Serververbindungen @@ -4444,11 +4536,23 @@ Fehler: %@ Das Senden von Sprachnachrichten nicht erlauben. No comment provided by engineer. + + Protect IP address + IP-Adresse schützen + No comment provided by engineer. + Protect app screen App-Bildschirm schützen No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + Schützen Sie Ihre IP-Adresse vor den Nachrichten-Relais , die Ihre Kontakte ausgewählt haben. +Aktivieren Sie es in den *Netzwerk & Server* Einstellungen. + No comment provided by engineer. + Protect your chat profiles with a password! Ihre Chat-Profile mit einem Passwort schützen! @@ -4554,11 +4658,6 @@ Fehler: %@ Die Empfängeradresse wird auf einen anderen Server geändert. Der Adresswechsel wird abgeschlossen, wenn der Absender wieder online ist. No comment provided by engineer. - - Receiving concurrency - Gleichzeitiger Empfang - No comment provided by engineer. - Receiving file will be stopped. Der Empfang der Datei wird beendet. @@ -4794,6 +4893,11 @@ Fehler: %@ SMP-Server No comment provided by engineer. + + Safely receive files + Dateien sicher empfangen + No comment provided by engineer. + Safer groups Sicherere Gruppen @@ -5019,6 +5123,16 @@ Fehler: %@ Live Nachricht senden No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + Nachrichten werden direkt versendet, wenn die IP-Adresse geschützt ist, und Ihr oder der Zielserver kein privates Routing unterstützt. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + Nachrichten werden direkt versendet, wenn Ihr oder der Zielserver kein privates Routing unterstützt. + No comment provided by engineer. + Send notifications Benachrichtigungen senden @@ -5124,6 +5238,11 @@ Fehler: %@ Gesendete Nachrichten werden nach der eingestellten Zeit gelöscht. No comment provided by engineer. + + Server address is incompatible with network settings. + Die Server-Adresse ist nicht mit den Netzwerk-Einstellungen kompatibel. + srv error text. + Server requires authorization to create queues, check password Um Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort @@ -5139,6 +5258,11 @@ Fehler: %@ Server Test ist fehlgeschlagen! No comment provided by engineer. + + Server version is incompatible with network settings. + Die Server-Version ist nicht mit den Netzwerk-Einstellungen kompatibel. + srv error text + Servers Server @@ -5259,11 +5383,21 @@ Fehler: %@ Letzte Nachrichten anzeigen No comment provided by engineer. + + Show message status + Nachrichtenstatus anzeigen + No comment provided by engineer. + Show preview Vorschau anzeigen No comment provided by engineer. + + Show → on messages sent via private routing. + Bei Nachrichten, die über privates Routing versendet wurden, → anzeigen. + No comment provided by engineer. + Show: Anzeigen: @@ -5586,6 +5720,11 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Wenn sie Nachrichten oder Kontaktanfragen empfangen, kann Sie die App benachrichtigen - Um dies zu aktivieren, öffnen Sie bitte die Einstellungen. No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + Die App wird eine Bestätigung bei Downloads von unbekannten Datei-Servern anfordern (außer bei .onion). + No comment provided by engineer. + The attempt to change database passphrase was not completed. Die Änderung des Datenbank-Passworts konnte nicht abgeschlossen werden. @@ -5771,6 +5910,11 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro Bild- und Sprachdateinamen enthalten UTC, um Informationen zur Zeitzone zu schützen. No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + Zum Schutz Ihrer IP-Adresse, wird für die Nachrichten-Auslieferung privates Routing über Ihre konfigurierten SMP-Server genutzt. + No comment provided by engineer. + To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled. @@ -5863,11 +6007,6 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Mitglied freigeben? No comment provided by engineer. - - Unexpected error: %@ - Unerwarteter Fehler: %@ - item status description - Unexpected migration state Unerwarteter Migrationsstatus @@ -5913,6 +6052,11 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt Unbekannter Fehler No comment provided by engineer. + + Unknown servers! + Unbekannte Server! + No comment provided by engineer. + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. Aktivieren Sie den Modus "Bitte nicht stören", um Unterbrechungen zu vermeiden, es sei denn, Sie verwenden die iOS Anrufschnittstelle. @@ -6032,7 +6176,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use current profile - Das aktuelle Profil nutzen + Aktuelles Profil nutzen No comment provided by engineer. @@ -6052,7 +6196,7 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Use new incognito profile - Ein neues Inkognito-Profil nutzen + Neues Inkognito-Profil nutzen No comment provided by engineer. @@ -6060,6 +6204,16 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Nur lokale Benachrichtigungen nutzen? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + Sie nutzen privates Routing mit unbekannten Servern, wenn Ihre IP-Adresse nicht geschützt ist. + No comment provided by engineer. + + + Use private routing with unknown servers. + Sie nutzen privates Routing mit unbekannten Servern. + No comment provided by engineer. + Use server Server nutzen @@ -6295,11 +6449,26 @@ Bitten Sie Ihren Kontakt darum einen weiteren Verbindungs-Link zu erzeugen, um s Mit reduziertem Akkuverbrauch. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für Datei-Server sichtbar sein. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für diese XFTP-Relais sichtbar sein: %@. + No comment provided by engineer. + Wrong database passphrase Falsches Datenbank-Passwort No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + Falscher Schlüssel oder unbekannte Verbindung - höchstwahrscheinlich ist diese Verbindung gelöscht worden. + snd error text + Wrong passphrase! Falsches Passwort! @@ -7415,6 +7584,12 @@ SimpleX-Server können Ihr Profil nicht einsehen. Direktnachricht senden No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address Es wurde eine neue Kontaktadresse festgelegt @@ -7455,11 +7630,21 @@ SimpleX-Server können Ihr Profil nicht einsehen. Unbekannt connection info + + unknown relays + Unbekannte Relais + No comment provided by engineer. + unknown status unbekannter Gruppenmitglieds-Status No comment provided by engineer. + + unprotected + Ungeschützt + No comment provided by engineer. + updated group profile Aktualisiertes Gruppenprofil @@ -7525,6 +7710,11 @@ SimpleX-Server können Ihr Profil nicht einsehen. Wochen time unit + + when IP hidden + Wenn die IP-Adresse versteckt ist + No comment provided by engineer. + yes Ja @@ -7661,7 +7851,7 @@ SimpleX-Server können Ihr Profil nicht einsehen. Copyright © 2022 SimpleX Chat. All rights reserved. - Copyright © 2022 SimpleX Chat. All rights reserved. + Copyright © 2024 SimpleX Chat. All rights reserved. Copyright (human-readable) 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 2b5677dd61..aca1aefb11 100644 --- a/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff +++ b/apps/ios/SimpleX Localizations/en.xcloc/Localized Contents/en.xliff @@ -713,6 +713,11 @@ Allow disappearing messages only if your contact allows it to you. No comment provided by engineer. + + Allow downgrade + Allow downgrade + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Allow irreversible message deletion only if your contact allows it to you. (24 hours) @@ -808,6 +813,11 @@ Already joining the group! No comment provided by engineer. + + Always use private routing. + Always use private routing. + No comment provided by engineer. + Always use relay Always use relay @@ -1088,6 +1098,11 @@ Cannot receive file No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + Capacity exceeded - recipient did not receive previously sent messages. + snd error text + Cellular Cellular @@ -1284,6 +1299,11 @@ Confirm database upgrades No comment provided by engineer. + + Confirm files from unknown servers. + Confirm files from unknown servers. + No comment provided by engineer. + Confirm network settings Confirm network settings @@ -1701,6 +1721,11 @@ This is your own one-time link! Database will be migrated when the app restarts No comment provided by engineer. + + Debug delivery + Debug delivery + No comment provided by engineer. + Decentralized Decentralized @@ -1948,6 +1973,11 @@ This cannot be undone! Desktop devices No comment provided by engineer. + + Destination server error: %@ + Destination server error: %@ + snd error text + Develop Develop @@ -2053,11 +2083,21 @@ This cannot be undone! Discover via local network No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + Do NOT send messages directly, even if your or destination server does not support private routing. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. Do NOT use SimpleX for emergency calls. No comment provided by engineer. + + Do NOT use private routing. + Do NOT use private routing. + No comment provided by engineer. + Do it later Do it later @@ -2621,7 +2661,7 @@ This cannot be undone! Error: %@ Error: %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2713,6 +2753,11 @@ This cannot be undone! File: %@ No comment provided by engineer. + + Files + Files + No comment provided by engineer. + Files & media Files & media @@ -2818,6 +2863,20 @@ This cannot be undone! Forwarded from No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + Forwarding server: %1$@ +Destination server error: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + Forwarding server: %1$@ +Error: %2$@ + snd error text + Found desktop Found desktop @@ -3633,11 +3692,21 @@ This is your link for group %@! Message delivery receipts! No comment provided by engineer. + + Message delivery warning + Message delivery warning + item status text + Message draft Message draft No comment provided by engineer. + + Message queue info + Message queue info + No comment provided by engineer. + Message reactions Message reactions @@ -3653,6 +3722,16 @@ This is your link for group %@! Message reactions are prohibited in this group. No comment provided by engineer. + + Message routing fallback + Message routing fallback + No comment provided by engineer. + + + Message routing mode + Message routing mode + No comment provided by engineer. + Message source remains private. Message source remains private. @@ -3783,11 +3862,6 @@ This is your link for group %@! Most likely this connection is deleted. item status description - - Most likely this contact has deleted the connection with you. - Most likely this contact has deleted the connection with you. - No comment provided by engineer. - Multiple chat profiles Multiple chat profiles @@ -3818,6 +3892,11 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + Network issues - message expired after many attempts to send it. + snd error text + Network management Network management @@ -4359,11 +4438,26 @@ Error: %@ Private filenames No comment provided by engineer. + + Private message routing + Private message routing + No comment provided by engineer. + + + Private message routing 🚀 + Private message routing 🚀 + No comment provided by engineer. + Private notes Private notes name of notes to self + + Private routing + Private routing + No comment provided by engineer. + Profile and server connections Profile and server connections @@ -4444,11 +4538,23 @@ Error: %@ Prohibit sending voice messages. No comment provided by engineer. + + Protect IP address + Protect IP address + No comment provided by engineer. + Protect app screen Protect app screen No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + No comment provided by engineer. + Protect your chat profiles with a password! Protect your chat profiles with a password! @@ -4554,11 +4660,6 @@ Error: %@ Receiving address will be changed to a different server. Address change will complete after sender comes online. No comment provided by engineer. - - Receiving concurrency - Receiving concurrency - No comment provided by engineer. - Receiving file will be stopped. Receiving file will be stopped. @@ -4794,6 +4895,11 @@ Error: %@ SMP servers No comment provided by engineer. + + Safely receive files + Safely receive files + No comment provided by engineer. + Safer groups Safer groups @@ -5019,6 +5125,16 @@ Error: %@ Send live message No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + Send messages directly when IP address is protected and your or destination server does not support private routing. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + Send messages directly when your or destination server does not support private routing. + No comment provided by engineer. + Send notifications Send notifications @@ -5124,6 +5240,11 @@ Error: %@ Sent messages will be deleted after set time. No comment provided by engineer. + + Server address is incompatible with network settings. + Server address is incompatible with network settings. + srv error text. + Server requires authorization to create queues, check password Server requires authorization to create queues, check password @@ -5139,6 +5260,11 @@ Error: %@ Server test failed! No comment provided by engineer. + + Server version is incompatible with network settings. + Server version is incompatible with network settings. + srv error text + Servers Servers @@ -5259,11 +5385,21 @@ Error: %@ Show last messages No comment provided by engineer. + + Show message status + Show message status + No comment provided by engineer. + Show preview Show preview No comment provided by engineer. + + Show → on messages sent via private routing. + Show → on messages sent via private routing. + No comment provided by engineer. + Show: Show: @@ -5586,6 +5722,11 @@ It can happen because of some bug or when the connection is compromised.The app can notify you when you receive messages or contact requests - please open settings to enable. No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + The app will ask to confirm downloads from unknown file servers (except .onion). + No comment provided by engineer. + The attempt to change database passphrase was not completed. The attempt to change database passphrase was not completed. @@ -5771,6 +5912,11 @@ It can happen because of some bug or when the connection is compromised.To protect timezone, image/voice files use UTC. No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + To protect your IP address, private routing uses your SMP servers to deliver messages. + 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. @@ -5863,11 +6009,6 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock member? No comment provided by engineer. - - Unexpected error: %@ - Unexpected error: %@ - item status description - Unexpected migration state Unexpected migration state @@ -5913,6 +6054,11 @@ You will be prompted to complete authentication before this feature is enabled.< Unknown error No comment provided by engineer. + + Unknown servers! + Unknown servers! + No comment provided by engineer. + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. @@ -6060,6 +6206,16 @@ To connect, please ask your contact to create another connection link and check Use only local notifications? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + Use private routing with unknown servers when IP address is not protected. + No comment provided by engineer. + + + Use private routing with unknown servers. + Use private routing with unknown servers. + No comment provided by engineer. + Use server Use server @@ -6295,11 +6451,26 @@ To connect, please ask your contact to create another connection link and check With reduced battery usage. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + Without Tor or VPN, your IP address will be visible to file servers. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + No comment provided by engineer. + Wrong database passphrase Wrong database passphrase No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + Wrong key or unknown connection - most likely this connection is deleted. + snd error text + Wrong passphrase! Wrong passphrase! @@ -7415,6 +7586,15 @@ SimpleX servers cannot see your profile. send direct message No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address set new contact address @@ -7455,11 +7635,21 @@ SimpleX servers cannot see your profile. unknown connection info + + unknown relays + unknown relays + No comment provided by engineer. + unknown status unknown status No comment provided by engineer. + + unprotected + unprotected + No comment provided by engineer. + updated group profile updated group profile @@ -7525,6 +7715,11 @@ SimpleX servers cannot see your profile. weeks time unit + + when IP hidden + when IP hidden + No comment provided by engineer. + yes yes 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 5d2482ae27..d76630ab3f 100644 --- a/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff +++ b/apps/ios/SimpleX Localizations/es.xcloc/Localized Contents/es.xliff @@ -713,6 +713,11 @@ Se permiten los mensajes temporales pero sólo si tu contacto también los permite para tí. No comment provided by engineer. + + Allow downgrade + Permitir versión anterior + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Se permite la eliminación irreversible de mensajes pero sólo si tu contacto también la permite para tí. (24 horas) @@ -808,6 +813,11 @@ ¡Ya en proceso de unirte al grupo! No comment provided by engineer. + + Always use private routing. + Usar siempre enrutamiento privado. + No comment provided by engineer. + Always use relay Usar siempre retransmisor @@ -1040,7 +1050,7 @@ By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). - Mediante perfil (por defecto) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). + Mediante perfil (predeterminado) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA). No comment provided by engineer. @@ -1088,6 +1098,11 @@ No se puede recibir el archivo No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + Capacidad excedida - el destinatario no ha recibido los mensajes previos. + snd error text + Cellular Móvil @@ -1284,6 +1299,11 @@ Confirmar actualizaciones de la bases de datos No comment provided by engineer. + + Confirm files from unknown servers. + Confirma archivos de servidores desconocidos. + No comment provided by engineer. + Confirm network settings Confirmar configuración de red @@ -1701,6 +1721,10 @@ This is your own one-time link! La base de datos migrará cuando se reinicie la aplicación No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized Descentralizada @@ -1948,6 +1972,11 @@ This cannot be undone! Ordenadores No comment provided by engineer. + + Destination server error: %@ + Error del servidor de destino: %@ + snd error text + Develop Desarrollo @@ -2053,11 +2082,21 @@ This cannot be undone! Descubrir en red local No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + NO enviar mensajes directamente incluso si tu servidor o el de destino no soportan enrutamiento privado. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. NO uses SimpleX para llamadas de emergencia. No comment provided by engineer. + + Do NOT use private routing. + NO usar enrutamiento privado. + No comment provided by engineer. + Do it later Hacer más tarde @@ -2205,7 +2244,7 @@ This cannot be undone! Enabled for - Activar para + Activado para No comment provided by engineer. @@ -2621,7 +2660,7 @@ This cannot be undone! Error: %@ Error: %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2713,6 +2752,11 @@ This cannot be undone! Archivo: %@ No comment provided by engineer. + + Files + Archivos + No comment provided by engineer. + Files & media Archivos y multimedia @@ -2818,6 +2862,20 @@ This cannot be undone! Reenviado por No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + Servidor de reenvío: %1$@ +Error del servidor de destino: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + Servidor de reenvío: %1$@ +Error: %2$@ + snd error text + Found desktop Ordenador encontrado @@ -3633,11 +3691,20 @@ This is your link for group %@! ¡Confirmación de entrega de mensajes! No comment provided by engineer. + + Message delivery warning + Aviso de entrega de mensaje + item status text + Message draft Borrador de mensaje No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions Reacciones a mensajes @@ -3653,6 +3720,16 @@ This is your link for group %@! Las reacciones a los mensajes no están permitidas en este grupo. No comment provided by engineer. + + Message routing fallback + Enrutamiento de mensajes alternativo + No comment provided by engineer. + + + Message routing mode + Modo de enrutamiento de mensajes + No comment provided by engineer. + Message source remains private. El autor del mensaje se mantiene privado. @@ -3783,11 +3860,6 @@ This is your link for group %@! Probablemente la conexión ha sido eliminada. item status description - - Most likely this contact has deleted the connection with you. - Lo más probable es que este contacto haya eliminado la conexión contigo. - No comment provided by engineer. - Multiple chat profiles Múltiples perfiles @@ -3818,6 +3890,11 @@ This is your link for group %@! Conexión de red No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + Problema en la red - el mensaje ha expirado tras muchos intentos de envío. + snd error text + Network management Gestión de la red @@ -4359,11 +4436,26 @@ Error: %@ Nombres de archivos privados No comment provided by engineer. + + Private message routing + Enrutamiento privado de mensajes + No comment provided by engineer. + + + Private message routing 🚀 + Enrutamiento privado de mensajes 🚀 + No comment provided by engineer. + Private notes Notas privadas name of notes to self + + Private routing + Enrutamiento privado + No comment provided by engineer. + Profile and server connections Datos del perfil y conexiones @@ -4376,7 +4468,7 @@ Error: %@ Profile images - Imágenes del perfil + Forma de los perfiles No comment provided by engineer. @@ -4444,11 +4536,23 @@ Error: %@ No se permiten mensajes de voz. No comment provided by engineer. + + Protect IP address + Proteger dirección IP + No comment provided by engineer. + Protect app screen Proteger la pantalla de la aplicación No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + Protege tu dirección IP de los servidores de retransmisión elegidos por tus contactos. +Actívalo en ajustes de *Servidores y Redes*. + No comment provided by engineer. + Protect your chat profiles with a password! ¡Protege tus perfiles con contraseña! @@ -4554,11 +4658,6 @@ Error: %@ La dirección de recepción pasará a otro servidor. El cambio se completará cuando el remitente esté en línea. No comment provided by engineer. - - Receiving concurrency - Concurrencia en la recepción - No comment provided by engineer. - Receiving file will be stopped. Se detendrá la recepción del archivo. @@ -4626,7 +4725,7 @@ Error: %@ Relay server is only used if necessary. Another party can observe your IP address. - El retransmisor sólo se usa en caso de necesidad. Un tercero podría ver tu IP. + El servidor de retransmisión sólo se usa en caso de necesidad. Un tercero podría ver tu IP. No comment provided by engineer. @@ -4716,7 +4815,7 @@ Error: %@ Reset to defaults - Restablecer valores por defecto + Restablecer valores predetarminados No comment provided by engineer. @@ -4794,6 +4893,11 @@ Error: %@ Servidores SMP No comment provided by engineer. + + Safely receive files + Recibe archivos de forma segura + No comment provided by engineer. + Safer groups Grupos más seguros @@ -5001,7 +5105,7 @@ Error: %@ Send direct message to connect - Enviar mensaje directo para conectar + Envia un mensaje para conectar No comment provided by engineer. @@ -5019,6 +5123,16 @@ Error: %@ Mensaje en vivo No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + Enviar mensajes directamente cuando tu dirección IP está protegida y tu servidor o el de destino no admitan enrutamiento privado. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + Enviar mensajes directamente cuando tu servidor o el de destino no admitan enrutamiento privado. + No comment provided by engineer. + Send notifications Enviar notificaciones @@ -5124,6 +5238,11 @@ Error: %@ Los mensajes enviados se eliminarán una vez transcurrido el tiempo establecido. No comment provided by engineer. + + Server address is incompatible with network settings. + La dirección del servidor es incompatible con la configuración de la red. + srv error text. + Server requires authorization to create queues, check password El servidor requiere autorización para crear colas, comprueba la contraseña @@ -5139,6 +5258,11 @@ Error: %@ ¡Error en prueba del servidor! No comment provided by engineer. + + Server version is incompatible with network settings. + La versión del servidor es incompatible con la configuración de red. + srv error text + Servers Servidores @@ -5259,11 +5383,21 @@ Error: %@ Mostrar último mensaje No comment provided by engineer. + + Show message status + Estado del mensaje + No comment provided by engineer. + Show preview Mostrar vista previa No comment provided by engineer. + + Show → on messages sent via private routing. + Mostrar → en mensajes con enrutamiento privado. + No comment provided by engineer. + Show: Mostrar: @@ -5586,6 +5720,11 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. La aplicación puede notificarte cuando recibas mensajes o solicitudes de contacto: por favor, abre la configuración para activarlo. No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + La aplicación pedirá que confirmes las descargas desde servidores de archivos desconocidos (excepto .onion). + No comment provided by engineer. + The attempt to change database passphrase was not completed. El intento de cambiar la contraseña de la base de datos no se ha completado. @@ -5771,6 +5910,11 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida. Para proteger la zona horaria, los archivos de imagen/voz usan la hora UTC. No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + Para proteger tu dirección IP, el enrutamiento privado usa tus servidores SMP para enviar mensajes. + 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. @@ -5863,11 +6007,6 @@ Se te pedirá que completes la autenticación antes de activar esta función.¿Desbloquear miembro? No comment provided by engineer. - - Unexpected error: %@ - Error inesperado: %@ - item status description - Unexpected migration state Estado de migración inesperado @@ -5913,6 +6052,11 @@ Se te pedirá que completes la autenticación antes de activar esta función.Error desconocido No comment provided by engineer. + + Unknown servers! + ¡Servidores desconocidos! + 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. @@ -6061,6 +6205,16 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb ¿Usar sólo notificaciones locales? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + Usar enrutamiento privado con servidores desconocidos cuando la dirección IP no está protegida. + No comment provided by engineer. + + + Use private routing with unknown servers. + Usar enrutamiento privado con servidores desconocidos. + No comment provided by engineer. + Use server Usar servidor @@ -6296,11 +6450,26 @@ Para conectarte, pide a tu contacto que cree otro enlace de conexión y comprueb Con uso reducido de batería. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + Sin Tor o VPN, tu dirección IP será visible para los servidores de archivos. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + Sin Tor o VPN, tu dirección IP será visible para estos servidores XFTP: %@. + No comment provided by engineer. + Wrong database passphrase Contraseña de base de datos incorrecta No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + Clave incorrecta o conexión desconocida - probablemente esta conexión fue eliminada. + snd error text + Wrong passphrase! ¡Contraseña incorrecta! @@ -6986,17 +7155,17 @@ Los servidores de SimpleX no pueden ver tu perfil. default (%@) - por defecto (%@) + predeterminado (%@) pref value default (no) - por defecto (no) + predeterminado (no) No comment provided by engineer. default (yes) - por defecto (sí) + predeterminado (sí) No comment provided by engineer. @@ -7416,6 +7585,12 @@ Los servidores de SimpleX no pueden ver tu perfil. Enviar mensaje directo No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address nueva dirección de contacto @@ -7456,11 +7631,21 @@ Los servidores de SimpleX no pueden ver tu perfil. desconocido connection info + + unknown relays + servidor de retransmisión desconocido + No comment provided by engineer. + unknown status estado desconocido No comment provided by engineer. + + unprotected + desprotegido + No comment provided by engineer. + updated group profile ha actualizado el perfil del grupo @@ -7526,6 +7711,11 @@ Los servidores de SimpleX no pueden ver tu perfil. semanas time unit + + when IP hidden + con IP oculta + No comment provided by engineer. + yes 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 a239bebbcf..77f9e29871 100644 --- a/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff +++ b/apps/ios/SimpleX Localizations/fi.xcloc/Localized Contents/fi.xliff @@ -685,6 +685,10 @@ Salli katoavat viestit vain, jos kontaktisi sallii sen sinulle. No comment provided by engineer. + + Allow downgrade + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Salli peruuttamaton viestien poisto vain, jos kontaktisi sallii ne sinulle. (24 tuntia) @@ -777,6 +781,10 @@ Already joining the group! No comment provided by engineer. + + Always use private routing. + No comment provided by engineer. + Always use relay Käytä aina relettä @@ -1040,6 +1048,10 @@ Tiedostoa ei voi vastaanottaa No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + snd error text + Cellular No comment provided by engineer. @@ -1231,6 +1243,10 @@ Vahvista tietokannan päivitykset No comment provided by engineer. + + Confirm files from unknown servers. + No comment provided by engineer. + Confirm network settings No comment provided by engineer. @@ -1624,6 +1640,10 @@ This is your own one-time link! Tietokanta siirretään, kun sovellus käynnistyy uudelleen No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized Hajautettu @@ -1863,6 +1883,10 @@ This cannot be undone! Desktop devices No comment provided by engineer. + + Destination server error: %@ + snd error text + Develop Kehitä @@ -1966,11 +1990,19 @@ This cannot be undone! Discover via local network No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. Älä käytä SimpleX-sovellusta hätäpuheluihin. No comment provided by engineer. + + Do NOT use private routing. + No comment provided by engineer. + Do it later Tee myöhemmin @@ -2509,7 +2541,7 @@ This cannot be undone! Error: %@ Virhe: %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2598,6 +2630,10 @@ This cannot be undone! Tiedosto: %@ No comment provided by engineer. + + Files + No comment provided by engineer. + Files & media Tiedostot & media @@ -2696,6 +2732,16 @@ This cannot be undone! Forwarded from No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + snd error text + Found desktop No comment provided by engineer. @@ -3480,11 +3526,19 @@ This is your link for group %@! Viestien toimituskuittaukset! No comment provided by engineer. + + Message delivery warning + item status text + Message draft Viestiluonnos No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions Viestireaktiot @@ -3500,6 +3554,14 @@ This is your link for group %@! Viestireaktiot ovat kiellettyjä tässä ryhmässä. No comment provided by engineer. + + Message routing fallback + No comment provided by engineer. + + + Message routing mode + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -3617,11 +3679,6 @@ This is your link for group %@! Todennäköisesti tämä yhteys on poistettu. item status description - - Most likely this contact has deleted the connection with you. - Todennäköisesti tämä kontakti on poistanut yhteyden sinuun. - No comment provided by engineer. - Multiple chat profiles Useita keskusteluprofiileja @@ -3651,6 +3708,10 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + snd error text + Network management No comment provided by engineer. @@ -4169,10 +4230,22 @@ Error: %@ Yksityiset tiedostonimet No comment provided by engineer. + + Private message routing + No comment provided by engineer. + + + Private message routing 🚀 + No comment provided by engineer. + Private notes name of notes to self + + Private routing + No comment provided by engineer. + Profile and server connections Profiili- ja palvelinyhteydet @@ -4249,11 +4322,20 @@ Error: %@ Estä ääniviestien lähettäminen. No comment provided by engineer. + + Protect IP address + No comment provided by engineer. + Protect app screen Suojaa sovellusnäyttö No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + No comment provided by engineer. + Protect your chat profiles with a password! Suojaa keskusteluprofiilisi salasanalla! @@ -4356,10 +4438,6 @@ Error: %@ Vastaanotto-osoite vaihdetaan toiseen palvelimeen. Osoitteenmuutos tehdään sen jälkeen, kun lähettäjä tulee verkkoon. No comment provided by engineer. - - Receiving concurrency - No comment provided by engineer. - Receiving file will be stopped. Tiedoston vastaanotto pysäytetään. @@ -4587,6 +4665,10 @@ Error: %@ SMP-palvelimet No comment provided by engineer. + + Safely receive files + No comment provided by engineer. + Safer groups No comment provided by engineer. @@ -4804,6 +4886,14 @@ Error: %@ Lähetä live-viesti No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + No comment provided by engineer. + Send notifications Lähetys ilmoitukset @@ -4908,6 +4998,10 @@ Error: %@ Lähetetyt viestit poistetaan asetetun ajan kuluttua. No comment provided by engineer. + + Server address is incompatible with network settings. + srv error text. + Server requires authorization to create queues, check password Palvelin vaatii valtuutuksen jonojen luomiseen, tarkista salasana @@ -4923,6 +5017,10 @@ Error: %@ Palvelintesti epäonnistui! No comment provided by engineer. + + Server version is incompatible with network settings. + srv error text + Servers Palvelimet @@ -5038,11 +5136,19 @@ Error: %@ Näytä viimeiset viestit No comment provided by engineer. + + Show message status + No comment provided by engineer. + Show preview Näytä esikatselu No comment provided by engineer. + + Show → on messages sent via private routing. + No comment provided by engineer. + Show: Näytä: @@ -5355,6 +5461,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Sovellus voi ilmoittaa sinulle, kun saat viestejä tai yhteydenottopyyntöjä - avaa asetukset ottaaksesi ne käyttöön. No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + No comment provided by engineer. + The attempt to change database passphrase was not completed. Tietokannan tunnuslauseen muuttamista ei suoritettu loppuun. @@ -5531,6 +5641,10 @@ Tämä voi johtua jostain virheestä tai siitä, että yhteys on vaarantunut.Aikavyöhykkeen suojaamiseksi kuva-/äänitiedostot käyttävät UTC:tä. No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + 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. @@ -5616,11 +5730,6 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote Unblock member? No comment provided by engineer. - - Unexpected error: %@ - Odottamaton virhe: %@ - item status description - Unexpected migration state Odottamaton siirtotila @@ -5666,6 +5775,10 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote Tuntematon virhe No comment provided by engineer. + + Unknown servers! + No comment provided by engineer. + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. Ellet käytä iOS:n puhelinkäyttöliittymää, ota Älä häiritse -tila käyttöön keskeytysten välttämiseksi. @@ -5806,6 +5919,14 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja Use only local notifications? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + No comment provided by engineer. + + + Use private routing with unknown servers. + No comment provided by engineer. + Use server Käytä palvelinta @@ -6023,11 +6144,23 @@ Jos haluat muodostaa yhteyden, pyydä kontaktiasi luomaan toinen yhteyslinkki ja With reduced battery usage. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + No comment provided by engineer. + Wrong database passphrase Väärä tietokannan tunnuslause No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + snd error text + Wrong passphrase! Väärä tunnuslause! @@ -7106,6 +7239,12 @@ SimpleX-palvelimet eivät näe profiiliasi. send direct message No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address profile update event chat item @@ -7142,10 +7281,18 @@ SimpleX-palvelimet eivät näe profiiliasi. tuntematon connection info + + unknown relays + No comment provided by engineer. + unknown status No comment provided by engineer. + + unprotected + No comment provided by engineer. + updated group profile päivitetty ryhmäprofiili @@ -7209,6 +7356,10 @@ SimpleX-palvelimet eivät näe profiiliasi. viikkoa time unit + + when IP hidden + No comment provided by engineer. + yes kyllä 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 ac7fc8cc8b..7e6c0d113d 100644 --- a/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff +++ b/apps/ios/SimpleX Localizations/fr.xcloc/Localized Contents/fr.xliff @@ -713,6 +713,11 @@ Autorise les messages éphémères seulement si votre contact vous l’autorise. No comment provided by engineer. + + Allow downgrade + Autoriser la rétrogradation + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Autoriser la suppression irréversible des messages uniquement si votre contact vous l'autorise. (24 heures) @@ -808,6 +813,11 @@ Groupe déjà rejoint ! No comment provided by engineer. + + Always use private routing. + Toujours utiliser le routage privé. + No comment provided by engineer. + Always use relay Se connecter via relais @@ -1088,6 +1098,11 @@ Impossible de recevoir le fichier No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + Capacité dépassée - le destinataire n'a pas pu recevoir les messages envoyés précédemment. + snd error text + Cellular Cellulaire @@ -1284,6 +1299,11 @@ Confirmer la mise à niveau de la base de données No comment provided by engineer. + + Confirm files from unknown servers. + Confirmer les fichiers provenant de serveurs inconnus. + No comment provided by engineer. + Confirm network settings Confirmer les paramètres réseau @@ -1701,6 +1721,10 @@ Il s'agit de votre propre lien unique ! La base de données sera migrée lors du redémarrage de l'app No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized Décentralisé @@ -1925,7 +1949,7 @@ Cette opération ne peut être annulée ! Delivery receipts! - Justificatifs de réception! + Justificatifs de réception ! No comment provided by engineer. @@ -1948,6 +1972,11 @@ Cette opération ne peut être annulée ! Appareils de bureau No comment provided by engineer. + + Destination server error: %@ + Erreur du serveur de destination : %@ + snd error text + Develop Développer @@ -2053,11 +2082,21 @@ Cette opération ne peut être annulée ! Rechercher sur le réseau No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + Ne pas envoyer de messages directement, même si votre serveur ou le serveur de destination ne prend pas en charge le routage privé. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. N'utilisez PAS SimpleX pour les appels d'urgence. No comment provided by engineer. + + Do NOT use private routing. + Ne pas utiliser de routage privé. + No comment provided by engineer. + Do it later Faites-le plus tard @@ -2621,7 +2660,7 @@ Cette opération ne peut être annulée ! Error: %@ Erreur : %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2713,6 +2752,11 @@ Cette opération ne peut être annulée ! Fichier : %@ No comment provided by engineer. + + Files + Fichiers + No comment provided by engineer. + Files & media Fichiers & médias @@ -2775,7 +2819,7 @@ Cette opération ne peut être annulée ! Fix connection? - Réparer la connexion? + Réparer la connexion ? No comment provided by engineer. @@ -2818,6 +2862,20 @@ Cette opération ne peut être annulée ! Transféré depuis No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + Serveur de transfert : %1$@ +Erreur du serveur de destination : %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + Serveur de transfert : %1$@ +Erreur : %2$@ + snd error text + Found desktop Bureau trouvé @@ -3633,11 +3691,20 @@ Voici votre lien pour le groupe %@ ! Accusés de réception des messages ! No comment provided by engineer. + + Message delivery warning + Avertissement sur la distribution des messages + item status text + Message draft Brouillon de message No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions Réactions aux messages @@ -3653,6 +3720,16 @@ Voici votre lien pour le groupe %@ ! Les réactions aux messages sont interdites dans ce groupe. No comment provided by engineer. + + Message routing fallback + Rabattement du routage des messages + No comment provided by engineer. + + + Message routing mode + Mode de routage des messages + No comment provided by engineer. + Message source remains private. La source du message reste privée. @@ -3783,11 +3860,6 @@ Voici votre lien pour le groupe %@ ! Connexion probablement supprimée. item status description - - Most likely this contact has deleted the connection with you. - Il est fort probable que ce contact ait supprimé la connexion avec vous. - No comment provided by engineer. - Multiple chat profiles Différents profils de chat @@ -3818,6 +3890,11 @@ Voici votre lien pour le groupe %@ ! Connexion au réseau No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + Problèmes de réseau - le message a expiré après plusieurs tentatives d'envoi. + snd error text + Network management Gestion du réseau @@ -4359,11 +4436,26 @@ Erreur : %@ Noms de fichiers privés No comment provided by engineer. + + Private message routing + Routage privé des messages + No comment provided by engineer. + + + Private message routing 🚀 + Routage privé des messages 🚀 + No comment provided by engineer. + Private notes Notes privées name of notes to self + + Private routing + Routage privé + No comment provided by engineer. + Profile and server connections Profil et connexions au serveur @@ -4444,11 +4536,23 @@ Erreur : %@ Interdire l'envoi de messages vocaux. No comment provided by engineer. + + Protect IP address + Protéger l'adresse IP + No comment provided by engineer. + Protect app screen Protéger l'écran de l'app No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + Protégez votre adresse IP des relais de messagerie choisis par vos contacts. +Activez-le dans les paramètres *Réseau et serveurs*. + No comment provided by engineer. + Protect your chat profiles with a password! Protégez vos profils de chat par un mot de passe ! @@ -4554,11 +4658,6 @@ Erreur : %@ L'adresse de réception sera changée pour un autre serveur. Le changement d'adresse sera terminé lorsque l'expéditeur sera en ligne. No comment provided by engineer. - - Receiving concurrency - Réception simultanée - No comment provided by engineer. - Receiving file will be stopped. La réception du fichier sera interrompue. @@ -4591,7 +4690,7 @@ Erreur : %@ Reconnect servers? - Reconnecter les serveurs? + Reconnecter les serveurs ? No comment provided by engineer. @@ -4666,7 +4765,7 @@ Erreur : %@ Renegotiate encryption? - Renégocier le chiffrement? + Renégocier le chiffrement ? No comment provided by engineer. @@ -4794,6 +4893,11 @@ Erreur : %@ Serveurs SMP No comment provided by engineer. + + Safely receive files + Réception de fichiers en toute sécurité + No comment provided by engineer. + Safer groups Groupes plus sûrs @@ -5019,6 +5123,16 @@ Erreur : %@ Envoyer un message dynamique No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + Envoyer les messages de manière directe lorsque l'adresse IP est protégée et que votre serveur ou le serveur de destination ne prend pas en charge le routage privé. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + Envoyez les messages de manière directe lorsque votre serveur ou le serveur de destination ne prend pas en charge le routage privé. + No comment provided by engineer. + Send notifications Envoi de notifications @@ -5124,6 +5238,11 @@ Erreur : %@ Les messages envoyés seront supprimés après une durée déterminée. No comment provided by engineer. + + Server address is incompatible with network settings. + L'adresse du serveur est incompatible avec les paramètres du réseau. + srv error text. + Server requires authorization to create queues, check password Le serveur requiert une autorisation pour créer des files d'attente, vérifiez le mot de passe @@ -5139,6 +5258,11 @@ Erreur : %@ Échec du test du serveur ! No comment provided by engineer. + + Server version is incompatible with network settings. + La version du serveur est incompatible avec les paramètres du réseau. + srv error text + Servers Serveurs @@ -5259,11 +5383,21 @@ Erreur : %@ Voir les derniers messages No comment provided by engineer. + + Show message status + Afficher le statut du message + No comment provided by engineer. + Show preview Afficher l'aperçu No comment provided by engineer. + + Show → on messages sent via private routing. + Afficher → sur les messages envoyés via le routage privé. + No comment provided by engineer. + Show: Afficher : @@ -5586,6 +5720,11 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. L'application peut vous avertir lorsque vous recevez des messages ou des demandes de contact - veuillez ouvrir les paramètres pour les activer. No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + L'application demandera de confirmer les téléchargements à partir de serveurs de fichiers inconnus (sauf .onion). + No comment provided by engineer. + The attempt to change database passphrase was not completed. La tentative de modification de la phrase secrète de la base de données n'a pas abouti. @@ -5771,6 +5910,11 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise. Pour préserver le fuseau horaire, les fichiers image/voix utilisent le système UTC. No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + Pour protéger votre adresse IP, le routage privé utilise vos serveurs SMP pour délivrer les messages. + No comment provided by engineer. + To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled. @@ -5863,11 +6007,6 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Débloquer ce membre ? No comment provided by engineer. - - Unexpected error: %@ - Erreur inattendue : %@ - item status description - Unexpected migration state État de la migration inattendu @@ -5913,6 +6052,11 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s Erreur inconnue No comment provided by engineer. + + Unknown servers! + Serveurs inconnus ! + No comment provided by engineer. + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. À moins que vous utilisiez l'interface d'appel d'iOS, activez le mode "Ne pas déranger" pour éviter les interruptions. @@ -6060,6 +6204,16 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Utilisation de notifications locales uniquement ? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + Utiliser le routage privé avec des serveurs inconnus lorsque l'adresse IP n'est pas protégée. + No comment provided by engineer. + + + Use private routing with unknown servers. + Utiliser le routage privé avec des serveurs inconnus. + No comment provided by engineer. + Use server Utiliser ce serveur @@ -6212,7 +6366,7 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien 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 + 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. @@ -6295,11 +6449,26 @@ Pour vous connecter, veuillez demander à votre contact de créer un autre lien Consommation réduite de la batterie. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + Sans Tor ou un VPN, votre adresse IP sera visible par les serveurs de fichiers. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + Sans Tor ni VPN, votre adresse IP sera visible par ces relais XFTP : %@. + No comment provided by engineer. + Wrong database passphrase Mauvaise phrase secrète pour la base de données No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + Clé erronée ou connexion non identifiée - il est très probable que cette connexion soit supprimée. + snd error text + Wrong passphrase! Mauvaise phrase secrète ! @@ -7415,6 +7584,12 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. envoyer un message direct No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address a changé d'adresse de contact @@ -7455,11 +7630,21 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. inconnu connection info + + unknown relays + relais inconnus + No comment provided by engineer. + unknown status statut inconnu No comment provided by engineer. + + unprotected + non protégé + No comment provided by engineer. + updated group profile mise à jour du profil de groupe @@ -7525,6 +7710,11 @@ Les serveurs SimpleX ne peuvent pas voir votre profil. semaines time unit + + when IP hidden + lorsque l'IP est masquée + No comment provided by engineer. + yes oui diff --git a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff index ad3148d891..13594c8efa 100644 --- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff +++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff @@ -349,7 +349,7 @@ **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). + **Legprivátabb**: ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást). No comment provided by engineer. @@ -364,7 +364,7 @@ **Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from. - **Javasolt**: az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési szerverre, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik. + **Javasolt**: az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik. No comment provided by engineer. @@ -570,7 +570,7 @@ Accept connection request? - Kapcsolatfelvétel elfogadása? + Kapcsolódási kérelem elfogadása? No comment provided by engineer. @@ -705,22 +705,27 @@ 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. + A hívások kezdeményezése kizárólag abban az esetben van engedélyezve, ha az 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. + Az eltűnő üzenetek küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi az ön számára. + No comment provided by engineer. + + + Allow downgrade + Korábbi verzióra történő visszatérés engedélyezése 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) + Az üzenetek végleges törlése kizárólag abban az esetben van engedélyezve, ha az 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. + Az üzenetreakciók küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. No comment provided by engineer. @@ -735,7 +740,7 @@ Allow sending disappearing messages. - Eltűnő üzenetek küldésének engedélyezése. + Az eltűnő üzenetek küldése engedélyezve van. No comment provided by engineer. @@ -760,7 +765,7 @@ 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. + A hangüzenetek küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. No comment provided by engineer. @@ -770,27 +775,27 @@ Allow your contacts adding message reactions. - Ismerősök általi üzenetreakciók hozzáadásának engedélyezése. + Az üzenetreakciók küldése engedélyezve van az ismerősei számára. No comment provided by engineer. Allow your contacts to call you. - Hívások engedélyezése ismerősök számára. + A hívások kezdeményezése engedélyezve van az ismerősei számára. No comment provided by engineer. Allow your contacts to irreversibly delete sent messages. (24 hours) - Elküldött üzenetek végleges törlésének engedélyezése az ismerősök számára. (24 óra) + Az elküldött üzenetek végleges törlése engedélyezve van az ismerősei 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. + Az eltűnő üzenetek küldésének engedélyezése az ismerősei 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. + A hangüzenetek küldése engedélyezve van az ismerősei számára. No comment provided by engineer. @@ -808,6 +813,11 @@ A csatlakozás folyamatban van a csoporthoz! No comment provided by engineer. + + Always use private routing. + Mindig használjon privát útválasztást. + No comment provided by engineer. + Always use relay Mindig használjon átjátszó kiszolgálót @@ -905,12 +915,12 @@ Audio/video calls are prohibited. - A hang- és videóhívások le vannak tiltva. + A hívások kezdeményezése le van tiltva ebben a csevegésben. No comment provided by engineer. Authentication cancelled - Hitelesítés megszakítva + Hitelesítés visszavonva PIN entry @@ -935,7 +945,7 @@ Auto-accept contact requests - Ismerős jelölések automatikus elfogadása + Kapcsolódási kérelmek automatikus elfogadása No comment provided by engineer. @@ -995,7 +1005,7 @@ Block member for all? - Tag letiltása mindenki számára? + Mindenki számára letiltja ezt a tagot? No comment provided by engineer. @@ -1020,7 +1030,7 @@ Both you and your contact can make calls. - Mindkét fél tud hívásokat indítani. + Mindkét fél tud hívásokat kezdeményezni. No comment provided by engineer. @@ -1088,6 +1098,11 @@ Nem lehet fogadni a fájlt No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + Kapacitás túllépés - a címzett nem kapta meg a korábban elküldött üzeneteket. + snd error text + Cellular Mobilhálózat @@ -1211,7 +1226,7 @@ Choose _Migrate from another device_ on the new device and scan QR code. - Válassza az _Átköltöztetés egy másik eszközről_ opciót az új eszközön és szkennelje be a QR-kódot. + Válassza az _Átköltöztetés egy másik eszközről_ opciót az új eszközön és olvassa be a QR-kódot. No comment provided by engineer. @@ -1231,17 +1246,17 @@ Clear conversation - Beszélgetés kiürítése + Üzenetek kiürítése No comment provided by engineer. Clear conversation? - Beszélgetés kiürítése? + Üzenetek kiürítése? No comment provided by engineer. Clear private notes? - Privát jegyzetek törlése? + Privát jegyzetek kiürítése? No comment provided by engineer. @@ -1284,6 +1299,11 @@ Adatbázis frissítés megerősítése No comment provided by engineer. + + Confirm files from unknown servers. + Ismeretlen kiszolgálókról származó fájlok jóváhagyása. + No comment provided by engineer. + Confirm network settings Hálózati beállítások megerősítése @@ -1465,7 +1485,7 @@ Ez az egyszer használatos hivatkozása! 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. + Az ismerősei törlésre jelölhetnek üzeneteket; ön majd meg tudja nézni azokat. No comment provided by engineer. @@ -1701,6 +1721,10 @@ Ez az egyszer használatos hivatkozása! Az adatbázis az alkalmazás újraindításakor migrálásra kerül No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized Decentralizált @@ -1738,7 +1762,7 @@ Ez az egyszer használatos hivatkozása! Delete after - Törlés miután + Törlés ennyi idő után No comment provided by engineer. @@ -1865,7 +1889,7 @@ Ez a művelet nem vonható vissza! Delete messages after - Üzenetek törlése miután + Üzenetek törlése ennyi idő után No comment provided by engineer. @@ -1948,6 +1972,11 @@ Ez a művelet nem vonható vissza! Számítógépek No comment provided by engineer. + + Destination server error: %@ + Célkiszolgáló hiba: %@ + snd error text + Develop Fejlesztés @@ -1985,7 +2014,7 @@ Ez a művelet nem vonható vissza! Direct messages between members are prohibited in this group. - Ebben a csoportban tiltott a tagok közötti közvetlen üzenetek küldése. + A közvetlen üzenetek küldése a tagok között le van tiltva ebben a csoportban. No comment provided by engineer. @@ -2015,7 +2044,7 @@ Ez a művelet nem vonható vissza! Disappearing messages are prohibited in this chat. - Az eltűnő üzenetek le vannak tiltva ebben a csevegésben. + Az eltűnő üzenetek küldése le van tiltva ebben a csevegésben. No comment provided by engineer. @@ -2053,11 +2082,21 @@ Ez a művelet nem vonható vissza! Felfedezés helyi hálózaton keresztül No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + Ne küldjön üzeneteket közvetlenül, még akkor sem, ha az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + 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 NOT use private routing. + Ne használjon privát útválasztást. + No comment provided by engineer. + Do it later Későbbre halaszt @@ -2621,7 +2660,7 @@ Ez a művelet nem vonható vissza! Error: %@ Hiba: %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2713,6 +2752,11 @@ Ez a művelet nem vonható vissza! Fájl: %@ No comment provided by engineer. + + Files + Fájlok + No comment provided by engineer. + Files & media Fájlok és média @@ -2818,6 +2862,20 @@ Ez a művelet nem vonható vissza! Továbbítva innen: No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + Továbbító kiszolgáló: %1$@ +Célkiszolgáló hiba:%2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + Továbbító kiszolgáló: %1$@ +Hiba: %2$@ + snd error text + Found desktop Megtalált számítógép @@ -3090,7 +3148,7 @@ Ez a művelet nem vonható vissza! 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: + Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot: No comment provided by engineer. @@ -3337,12 +3395,12 @@ Ez a művelet nem vonható vissza! Irreversible message deletion is prohibited in this chat. - Ebben a csevegésben az üzenetek végleges törlése le van tiltva. + Az üzenetek végleges törlése le van tiltva ebben a csevegésben. No comment provided by engineer. Irreversible message deletion is prohibited in this group. - Ebben a csoportban az üzenetek végleges törlése le van tiltva. + Az üzenetek végleges törlése le van tiltva ebben a csoportban. No comment provided by engineer. @@ -3470,7 +3528,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Leave - Elhagy + Elhagyás No comment provided by engineer. @@ -3565,7 +3623,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! 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 (%@). + Győződjön meg arról, hogy a %@ kiszolgálócímek megfelelő formátumúak, sorszeparáltak és nem duplikáltak (%@). No comment provided by engineer. @@ -3585,12 +3643,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Mark read - Megjelölés olvasottként + Olvasottnak jelölés No comment provided by engineer. Mark verified - Ellenőrzöttként jelölve + Hitelesítés No comment provided by engineer. @@ -3633,11 +3691,20 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Üzenetkézbesítési bizonylatok! No comment provided by engineer. + + Message delivery warning + Üzenet kézbesítési figyelmeztetés + item status text + Message draft Üzenetvázlat No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions Üzenetreakciók @@ -3645,12 +3712,22 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Message reactions are prohibited in this chat. - Az üzenetreakciók ebben a csevegésben le vannak tiltva. + Az üzenetreakciók küldése le van tiltva ebben a csevegésben. No comment provided by engineer. Message reactions are prohibited in this group. - Ebben a csoportban az üzenetreakciók le vannak tiltva. + Az üzenetreakciók küldése le van tiltva ebben a csoportban. + No comment provided by engineer. + + + Message routing fallback + Üzenet útválasztási tartalék + No comment provided by engineer. + + + Message routing mode + Üzenet útválasztási mód No comment provided by engineer. @@ -3685,12 +3762,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery. - Az üzeneteket, fájlokat és hívásokat **végpontok közötti titkosítással** és 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. + Az üzeneteket, fájlokat és hívásokat **végpontok közötti titkosítással**, sérülés utáni titkosság-védelemmel és -helyreállítással, továbbá visszautasítással védi. No comment provided by engineer. Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery. - Az üzeneteket, fájlokat és hívásokat **végpontok közötti kvantumrezisztens titkosítással** é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. + Az üzeneteket, fájlokat és hívásokat **végpontok közötti kvantumrezisztens titkosítással**, sérülés utáni titkosság-védelemmel és -helyreállítással, továbbá visszautasítással védi. No comment provided by engineer. @@ -3783,11 +3860,6 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! 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 @@ -3818,6 +3890,11 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Internetkapcsolat No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + Hálózati problémák - az üzenet többszöri elküldési kísérlet után lejárt. + snd error text + Network management Hálózatkezelés @@ -4079,7 +4156,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz! Only your contact can send disappearing messages. - Csak az ismerős tud eltűnő üzeneteket küldeni. + Csak az ismerőse tud eltűnő üzeneteket küldeni. No comment provided by engineer. @@ -4359,11 +4436,26 @@ Hiba: %@ Privát fájl nevek No comment provided by engineer. + + Private message routing + Privát üzenet útválasztás + No comment provided by engineer. + + + Private message routing 🚀 + Privát üzenet útválasztás 🚀 + No comment provided by engineer. + Private notes Privát jegyzetek name of notes to self + + Private routing + Privát útválasztás + No comment provided by engineer. + Profile and server connections Profil és kiszolgálókapcsolatok @@ -4401,7 +4493,7 @@ Hiba: %@ Prohibit audio/video calls. - Hang- és videóhívások tiltása. + A hívások kezdeményezése le van tiltva. No comment provided by engineer. @@ -4411,7 +4503,7 @@ Hiba: %@ Prohibit message reactions. - Üzenetreakciók tiltása. + Az üzenetreakciók küldése le van tiltva. No comment provided by engineer. @@ -4426,12 +4518,12 @@ Hiba: %@ Prohibit sending direct messages to members. - Közvetlen üzenetek küldésének letiltása a tagok számára. + A közvetlen üzenetek küldése le van tiltva a tagok között. No comment provided by engineer. Prohibit sending disappearing messages. - Eltűnő üzenetek küldésének letiltása. + Az eltűnő üzenetek küldése le van tiltva. No comment provided by engineer. @@ -4441,7 +4533,12 @@ Hiba: %@ Prohibit sending voice messages. - Hangüzenetek küldésének letiltása. + A hangüzenetek küldése le van tiltva. + No comment provided by engineer. + + + Protect IP address + Az IP-cím védelme No comment provided by engineer. @@ -4449,6 +4546,13 @@ Hiba: %@ Alkalmazás képernyőjének védelme No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + Védje IP-címét az ismerősei által kiválasztott üzenetküldő átjátszókkal szemben. +Engedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban. + No comment provided by engineer. + Protect your chat profiles with a password! Csevegési profiljok védelme jelszóval! @@ -4554,11 +4658,6 @@ Hiba: %@ 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 concurrency - Egyidejű fogadás - No comment provided by engineer. - Receiving file will be stopped. A fájl fogadása leállt. @@ -4641,12 +4740,12 @@ Hiba: %@ Remove member - Tag eltávolítása + Eltávolítás No comment provided by engineer. Remove member? - Tag eltávolítása? + Biztosan eltávolítja? No comment provided by engineer. @@ -4794,6 +4893,11 @@ Hiba: %@ Üzenetküldő (SMP) kiszolgálók No comment provided by engineer. + + Safely receive files + Fájlok biztonságos fogadása + No comment provided by engineer. + Safer groups Biztonságosabb csoportok @@ -4911,7 +5015,7 @@ Hiba: %@ Scan code - Kód beolvasása + Beolvasás No comment provided by engineer. @@ -5019,6 +5123,16 @@ Hiba: %@ Élő üzenet küldése No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + Közvetlen üzenetküldés, ha az IP-cím védett és az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + Közvetlen üzenetküldés, ha az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + No comment provided by engineer. + Send notifications Értesítések küldése @@ -5051,7 +5165,7 @@ Hiba: %@ Sender cancelled file transfer. - A küldő megszakította a fájl átvitelt. + A fájl küldője visszavonta az átvitelt. No comment provided by engineer. @@ -5124,6 +5238,11 @@ Hiba: %@ Az elküldött üzenetek törlésre kerülnek a beállított idő után. No comment provided by engineer. + + Server address is incompatible with network settings. + A kiszolgáló címe nem kompatibilis a hálózati beállításokkal. + srv error text. + 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 @@ -5139,6 +5258,11 @@ Hiba: %@ Sikertelen kiszolgáló-teszt! No comment provided by engineer. + + Server version is incompatible with network settings. + A kiszolgáló verziója nem kompatibilis a hálózati beállításokkal. + srv error text + Servers Kiszolgálók @@ -5259,11 +5383,21 @@ Hiba: %@ Utolsó üzenetek megjelenítése No comment provided by engineer. + + Show message status + Üzenet állapot megjelenítése + No comment provided by engineer. + Show preview Előnézet megjelenítése No comment provided by engineer. + + Show → on messages sent via private routing. + Egy „→” jel megjelenítése a privát útválasztáson keresztül küldött üzeneteknél. + No comment provided by engineer. + Show: Megjelenítés: @@ -5326,7 +5460,7 @@ Hiba: %@ SimpleX links are prohibited in this group. - A SimpleX hivatkozások küldése ebben a csoportban le van tiltva. + A SimpleX hivatkozások küldése le van tiltva ebben a csoportban. No comment provided by engineer. @@ -5586,6 +5720,11 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. 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 app will ask to confirm downloads from unknown file servers (except .onion). + Az alkalmazás kérni fogja az ismeretlen fájlkiszolgálókról (kivéve .onion) történő letöltések megerősítését. + 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. @@ -5598,7 +5737,7 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. The connection you accepted will be cancelled! - Az ön által elfogadott kapcsolat megszakad! + Az ön által elfogadott kapcsolat vissza lesz vonva! No comment provided by engineer. @@ -5771,6 +5910,11 @@ Ez valamilyen hiba, vagy sérült kapcsolat esetén fordulhat elő. 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 IP address, private routing uses your SMP servers to deliver messages. + Az IP-címe védelme érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez. + No comment provided by engineer. + To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled. @@ -5795,7 +5939,7 @@ A funkció engedélyezése előtt a rendszer felszólítja a hitelesítés befej 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. + A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) az ismerőse eszközén lévő kóddal. No comment provided by engineer. @@ -5863,11 +6007,6 @@ A funkció engedélyezése előtt a rendszer felszólítja a hitelesítés befej Tag feloldása? No comment provided by engineer. - - Unexpected error: %@ - Váratlan hiba: %@ - item status description - Unexpected migration state Váratlan átköltöztetési állapot @@ -5913,6 +6052,11 @@ A funkció engedélyezése előtt a rendszer felszólítja a hitelesítés befej Ismeretlen hiba No comment provided by engineer. + + Unknown servers! + Ismeretlen kiszolgálók! + 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. @@ -5987,7 +6131,7 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol 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. + A beállítások frissítése a kiszolgálókhoz való újra kapcsolódással jár. No comment provided by engineer. @@ -6060,6 +6204,16 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Csak helyi értesítések használata? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + Privát útválasztás használata ismeretlen kiszolgálókkal, ha az IP-cím nem védett. + No comment provided by engineer. + + + Use private routing with unknown servers. + Használjon privát útválasztást ismeretlen kiszolgálókkal. + No comment provided by engineer. + Use server Kiszolgáló használata @@ -6167,7 +6321,7 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Voice messages are prohibited in this chat. - A hangüzenetek le vannak tiltva ebben a csevegésben. + A hangüzenetek küldése le van tiltva ebben a csevegésben. No comment provided by engineer. @@ -6295,11 +6449,26 @@ A kapcsolódáshoz kérje meg ismerősét, hogy hozzon létre egy másik kapcsol Csökkentett akkumulátorhasználattal. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + Tor vagy VPN nélkül az IP-címe látható lesz a fájlkiszolgálók számára. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + Tor vagy VPN nélkül az IP-címe látható lesz ezen XFTP átjátszók számára: %@. + No comment provided by engineer. + Wrong database passphrase Téves adatbázis jelmondat No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + Rossz kulcs vagy ismeretlen kapcsolat - valószínűleg ez a kapcsolat törlődött. + snd error text + Wrong passphrase! Téves jelmondat! @@ -6474,7 +6643,7 @@ Csatlakozási kérés megismétlése? 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. + Ön szabályozhatja, hogy mely kiszogál(ók)ón keresztül **kapja** az üzeneteket, az ismerősöket - az üzenetküldéshez használt kiszolgálókon. No comment provided by engineer. @@ -6642,8 +6811,8 @@ Kapcsolódási kérés megismétlése? 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). + Az ismerősének online kell lennie ahhoz, hogy a kapcsolat létrejöjjön. +Visszavonhatja 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. @@ -6850,7 +7019,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. cancelled %@ - %@ törölve + %@ visszavonva feature offered item @@ -6965,7 +7134,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. creator - szerző + készítő No comment provided by engineer. @@ -7185,12 +7354,12 @@ A SimpleX kiszolgálók nem látjhatják profilját. invited - meghívta + meghíva No comment provided by engineer. invited %@ - meghívta %@-t + meghívta őt: %@ rcv group event chat item @@ -7200,7 +7369,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. invited via your group link - meghívta a csoport hivatkozásán keresztül + meghíva az ön csoport hivatkozásán keresztül rcv group event chat item @@ -7415,6 +7584,12 @@ A SimpleX kiszolgálók nem látjhatják profilját. közvetlen üzenet küldése No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address új kapcsolattartási azonosító beállítása @@ -7455,14 +7630,24 @@ A SimpleX kiszolgálók nem látjhatják profilját. ismeretlen connection info + + unknown relays + ismeretlen átjátszók + No comment provided by engineer. + unknown status ismeretlen státusz No comment provided by engineer. + + unprotected + nem védett + No comment provided by engineer. + updated group profile - módosított csoport profil + frissítette a csoport profilját rcv group event chat item @@ -7507,7 +7692,7 @@ A SimpleX kiszolgálók nem látjhatják profilját. waiting for answer… - várakozás válaszra… + várakozás a válaszra… No comment provided by engineer. @@ -7525,6 +7710,11 @@ A SimpleX kiszolgálók nem látjhatják profilját. hét time unit + + when IP hidden + ha az IP-cím rejtett + No comment provided by engineer. + yes igen 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 a5fe0ec830..b6fd18f551 100644 --- a/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff +++ b/apps/ios/SimpleX Localizations/it.xcloc/Localized Contents/it.xliff @@ -713,6 +713,11 @@ Consenti i messaggi a tempo solo se il contatto li consente a te. No comment provided by engineer. + + Allow downgrade + Consenti downgrade + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Consenti l'eliminazione irreversibile dei messaggi solo se il contatto la consente a te. (24 ore) @@ -808,6 +813,11 @@ Già in ingresso nel gruppo! No comment provided by engineer. + + Always use private routing. + Usa sempre l'instradamento privato. + No comment provided by engineer. + Always use relay Connetti via relay @@ -1088,6 +1098,11 @@ Impossibile ricevere il file No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + Quota superata - il destinatario non ha ricevuto i messaggi precedentemente inviati. + snd error text + Cellular Mobile @@ -1284,6 +1299,11 @@ Conferma aggiornamenti database No comment provided by engineer. + + Confirm files from unknown servers. + Conferma i file da server sconosciuti. + No comment provided by engineer. + Confirm network settings Conferma le impostazioni di rete @@ -1701,6 +1721,10 @@ Questo è il tuo link una tantum! Il database verrà migrato al riavvio dell'app No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized Decentralizzato @@ -1948,6 +1972,11 @@ Non è reversibile! Dispositivi desktop No comment provided by engineer. + + Destination server error: %@ + Errore del server di destinazione: %@ + snd error text + Develop Sviluppa @@ -2053,11 +2082,21 @@ Non è reversibile! Individua via rete locale No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + NON inviare messaggi direttamente, anche se il tuo server o quello di destinazione non supporta l'instradamento privato. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. NON usare SimpleX per chiamate di emergenza. No comment provided by engineer. + + Do NOT use private routing. + NON usare l'instradamento privato. + No comment provided by engineer. + Do it later Fallo dopo @@ -2621,7 +2660,7 @@ Non è reversibile! Error: %@ Errore: %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2713,6 +2752,11 @@ Non è reversibile! File: %@ No comment provided by engineer. + + Files + File + No comment provided by engineer. + Files & media File e multimediali @@ -2818,6 +2862,20 @@ Non è reversibile! Inoltrato da No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + Server di inoltro: %1$@ +Errore del server di destinazione: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + Server di inoltro: %1$@ +Errore: %2$@ + snd error text + Found desktop Desktop trovato @@ -3633,11 +3691,20 @@ Questo è il tuo link per il gruppo %@! Ricevute di consegna dei messaggi! No comment provided by engineer. + + Message delivery warning + Avviso di consegna del messaggio + item status text + Message draft Bozza dei messaggi No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions Reazioni ai messaggi @@ -3653,6 +3720,16 @@ Questo è il tuo link per il gruppo %@! Le reazioni ai messaggi sono vietate in questo gruppo. No comment provided by engineer. + + Message routing fallback + Ripiego instradamento messaggio + No comment provided by engineer. + + + Message routing mode + Modalità instradamento messaggio + No comment provided by engineer. + Message source remains private. La fonte del messaggio resta privata. @@ -3783,11 +3860,6 @@ Questo è il tuo link per il gruppo %@! Probabilmente questa connessione è stata eliminata. item status description - - Most likely this contact has deleted the connection with you. - Probabilmente questo contatto ha eliminato la connessione con te. - No comment provided by engineer. - Multiple chat profiles Profili di chat multipli @@ -3818,6 +3890,11 @@ Questo è il tuo link per il gruppo %@! Connessione di rete No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + Problemi di rete - messaggio scaduto dopo molti tentativi di inviarlo. + snd error text + Network management Gestione della rete @@ -4359,11 +4436,26 @@ Errore: %@ Nomi di file privati No comment provided by engineer. + + Private message routing + Instradamento privato messaggi + No comment provided by engineer. + + + Private message routing 🚀 + Instradamento privato dei messaggi 🚀 + No comment provided by engineer. + Private notes Note private name of notes to self + + Private routing + Instradamento privato + No comment provided by engineer. + Profile and server connections Profilo e connessioni al server @@ -4444,11 +4536,23 @@ Errore: %@ Proibisci l'invio di messaggi vocali. No comment provided by engineer. + + Protect IP address + Proteggi l'indirizzo IP + No comment provided by engineer. + Protect app screen Proteggi la schermata dell'app No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + Proteggi il tuo indirizzo IP dai relay di messaggistica scelti dai tuoi contatti. +Attivalo nelle impostazioni *Rete e server*. + No comment provided by engineer. + Protect your chat profiles with a password! Proteggi i tuoi profili di chat con una password! @@ -4554,11 +4658,6 @@ Errore: %@ L'indirizzo di ricezione verrà cambiato in un server diverso. La modifica dell'indirizzo verrà completata dopo che il mittente sarà in linea. No comment provided by engineer. - - Receiving concurrency - Ricezione concomitanza - No comment provided by engineer. - Receiving file will be stopped. La ricezione del file verrà interrotta. @@ -4794,6 +4893,11 @@ Errore: %@ Server SMP No comment provided by engineer. + + Safely receive files + Ricevi i file in sicurezza + No comment provided by engineer. + Safer groups Gruppi più sicuri @@ -5019,6 +5123,16 @@ Errore: %@ Invia messaggio in diretta No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + Invia messaggi direttamente quando l'indirizzo IP è protetto e il tuo server o quello di destinazione non supporta l'instradamento privato. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + Invia messaggi direttamente quando il tuo server o quello di destinazione non supporta l'instradamento privato. + No comment provided by engineer. + Send notifications Invia notifiche @@ -5124,6 +5238,11 @@ Errore: %@ I messaggi inviati verranno eliminati dopo il tempo impostato. No comment provided by engineer. + + Server address is incompatible with network settings. + L'indirizzo del server non è compatibile con le impostazioni di rete. + srv error text. + Server requires authorization to create queues, check password Il server richiede l'autorizzazione di creare code, controlla la password @@ -5139,6 +5258,11 @@ Errore: %@ Test del server fallito! No comment provided by engineer. + + Server version is incompatible with network settings. + La versione del server non è compatibile con le impostazioni di rete. + srv error text + Servers Server @@ -5259,11 +5383,21 @@ Errore: %@ Mostra ultimi messaggi No comment provided by engineer. + + Show message status + Mostra stato del messaggio + No comment provided by engineer. + Show preview Mostra anteprima No comment provided by engineer. + + Show → on messages sent via private routing. + Mostra → nei messaggi inviati via instradamento privato. + No comment provided by engineer. + Show: Mostra: @@ -5586,6 +5720,11 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.L'app può avvisarti quando ricevi messaggi o richieste di contatto: apri le impostazioni per attivare. No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + L'app chiederà di confermare i download da server di file sconosciuti (eccetto .onion). + No comment provided by engineer. + The attempt to change database passphrase was not completed. Il tentativo di cambiare la password del database non è stato completato. @@ -5771,6 +5910,11 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.Per proteggere il fuso orario, i file immagine/vocali usano UTC. No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + Per proteggere il tuo indirizzo IP, l'instradamento privato usa i tuoi server SMP per consegnare i messaggi. + No comment provided by engineer. + To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled. @@ -5863,11 +6007,6 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Sbloccare il membro? No comment provided by engineer. - - Unexpected error: %@ - Errore imprevisto: % @ - item status description - Unexpected migration state Stato di migrazione imprevisto @@ -5913,6 +6052,11 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio Errore sconosciuto No comment provided by engineer. + + Unknown servers! + Server sconosciuti! + No comment provided by engineer. + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. A meno che non utilizzi l'interfaccia di chiamata iOS, attiva la modalità Non disturbare per evitare interruzioni. @@ -6060,6 +6204,16 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Usare solo notifiche locali? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + Usa l'instradamento privato con server sconosciuti quando l'indirizzo IP non è protetto. + No comment provided by engineer. + + + Use private routing with unknown servers. + Usa l'instradamento privato con server sconosciuti. + No comment provided by engineer. + Use server Usa il server @@ -6295,11 +6449,26 @@ Per connetterti, chiedi al tuo contatto di creare un altro link di connessione e Con consumo di batteria ridotto. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + Senza Tor o VPN, il tuo indirizzo IP sarà visibile ai server di file. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + Senza Tor o VPN, il tuo indirizzo IP sarà visibile a questi relay XFTP: %@. + No comment provided by engineer. + Wrong database passphrase Password del database sbagliata No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + Chiave sbagliata o connessione sconosciuta - molto probabilmente questa connessione è stata eliminata. + snd error text + Wrong passphrase! Password sbagliata! @@ -7415,6 +7584,12 @@ I server di SimpleX non possono vedere il tuo profilo. invia messaggio diretto No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address impostato nuovo indirizzo di contatto @@ -7455,11 +7630,21 @@ I server di SimpleX non possono vedere il tuo profilo. sconosciuto connection info + + unknown relays + relay sconosciuti + No comment provided by engineer. + unknown status stato sconosciuto No comment provided by engineer. + + unprotected + non protetto + No comment provided by engineer. + updated group profile ha aggiornato il profilo del gruppo @@ -7525,6 +7710,11 @@ I server di SimpleX non possono vedere il tuo profilo. settimane time unit + + when IP hidden + quando l'IP è nascosto + No comment provided by engineer. + yes 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 3f32998707..395fcb9326 100644 --- a/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff +++ b/apps/ios/SimpleX Localizations/ja.xcloc/Localized Contents/ja.xliff @@ -703,6 +703,10 @@ 連絡先が許可している場合のみ消えるメッセージを許可する。 No comment provided by engineer. + + Allow downgrade + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) 送信相手も永久メッセージ削除を許可する時のみに許可する。 @@ -798,6 +802,10 @@ すでにグループに参加しています! No comment provided by engineer. + + Always use private routing. + No comment provided by engineer. + Always use relay 常にリレーを経由する @@ -1064,6 +1072,10 @@ ファイル受信ができません No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + snd error text + Cellular No comment provided by engineer. @@ -1255,6 +1267,10 @@ データベースのアップグレードを確認 No comment provided by engineer. + + Confirm files from unknown servers. + No comment provided by engineer. + Confirm network settings No comment provided by engineer. @@ -1648,6 +1664,10 @@ This is your own one-time link! データベースはアプリ再起動時に移行されます No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized 分散型 @@ -1887,6 +1907,10 @@ This cannot be undone! Desktop devices No comment provided by engineer. + + Destination server error: %@ + snd error text + Develop 開発 @@ -1990,11 +2014,19 @@ This cannot be undone! Discover via local network No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. 緊急通報にSimpleXを使用しないでください。 No comment provided by engineer. + + Do NOT use private routing. + No comment provided by engineer. + Do it later 後で行う @@ -2534,7 +2566,7 @@ This cannot be undone! Error: %@ エラー : %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2623,6 +2655,10 @@ This cannot be undone! ファイル: %@ No comment provided by engineer. + + Files + No comment provided by engineer. + Files & media ファイルとメディア @@ -2721,6 +2757,16 @@ This cannot be undone! Forwarded from No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + snd error text + Found desktop No comment provided by engineer. @@ -3504,11 +3550,19 @@ This is your link for group %@! Message delivery receipts! No comment provided by engineer. + + Message delivery warning + item status text + Message draft メッセージの下書き No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions メッセージへのリアクション @@ -3524,6 +3578,14 @@ This is your link for group %@! このグループではメッセージへのリアクションは禁止されています。 No comment provided by engineer. + + Message routing fallback + No comment provided by engineer. + + + Message routing mode + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -3641,11 +3703,6 @@ This is your link for group %@! おそらく、この接続は削除されています。 item status description - - Most likely this contact has deleted the connection with you. - 恐らくこの連絡先があなたとの接続を削除しました。 - No comment provided by engineer. - Multiple chat profiles 複数チャットのプロフィール @@ -3675,6 +3732,10 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + snd error text + Network management No comment provided by engineer. @@ -4195,10 +4256,22 @@ Error: %@ プライベートなファイル名 No comment provided by engineer. + + Private message routing + No comment provided by engineer. + + + Private message routing 🚀 + No comment provided by engineer. + Private notes name of notes to self + + Private routing + No comment provided by engineer. + Profile and server connections プロフィールとサーバ接続 @@ -4275,11 +4348,20 @@ Error: %@ 音声メッセージを使用禁止にする。 No comment provided by engineer. + + Protect IP address + No comment provided by engineer. + Protect app screen アプリ画面を守る No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + No comment provided by engineer. + Protect your chat profiles with a password! チャットのプロフィールをパスワードで保護します! @@ -4381,10 +4463,6 @@ Error: %@ 開発中の機能です!相手のクライアントが4.2でなければ機能しません。アドレス変更が完了すると、会話にメッセージが出ます。連絡相手 (またはグループのメンバー) からメッセージを受信できないかをご確認ください。 No comment provided by engineer. - - Receiving concurrency - No comment provided by engineer. - Receiving file will be stopped. ファイルの受信を停止します。 @@ -4612,6 +4690,10 @@ Error: %@ SMPサーバ No comment provided by engineer. + + Safely receive files + No comment provided by engineer. + Safer groups No comment provided by engineer. @@ -4829,6 +4911,14 @@ Error: %@ ライブメッセージを送信 No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + No comment provided by engineer. + Send notifications 通知を送信する @@ -4926,6 +5016,10 @@ Error: %@ 一定時間が経ったら送信されたメッセージが削除されます。 No comment provided by engineer. + + Server address is incompatible with network settings. + srv error text. + Server requires authorization to create queues, check password キューを作成するにはサーバーの認証が必要です。パスワードを確認してください @@ -4941,6 +5035,10 @@ Error: %@ サーバテスト失敗! No comment provided by engineer. + + Server version is incompatible with network settings. + srv error text + Servers サーバ @@ -5056,11 +5154,19 @@ Error: %@ 最新のメッセージを表示 No comment provided by engineer. + + Show message status + No comment provided by engineer. + Show preview プレビューを表示 No comment provided by engineer. + + Show → on messages sent via private routing. + No comment provided by engineer. + Show: 表示する: @@ -5374,6 +5480,10 @@ It can happen because of some bug or when the connection is compromised.アプリは、メッセージや連絡先のリクエストを受信したときに通知することができます - 設定を開いて有効にしてください。 No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + No comment provided by engineer. + The attempt to change database passphrase was not completed. データベースのパスフレーズ変更が完了してません。 @@ -5549,6 +5659,10 @@ It can happen because of some bug or when the connection is compromised.時間帯を漏らさないために、画像と音声ファイルはUTCを使います。 No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + 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. @@ -5634,11 +5748,6 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock member? No comment provided by engineer. - - Unexpected error: %@ - 予期しないエラー: %@ - item status description - Unexpected migration state 予期しない移行状態 @@ -5684,6 +5793,10 @@ You will be prompted to complete authentication before this feature is enabled.< 不明なエラー No comment provided by engineer. + + Unknown servers! + No comment provided by engineer. + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. iOS 通話インターフェイスを使用しない場合は、中断を避けるために「おやすみモード」を有効にしてください。 @@ -5824,6 +5937,14 @@ To connect, please ask your contact to create another connection link and check Use only local notifications? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + No comment provided by engineer. + + + Use private routing with unknown servers. + No comment provided by engineer. + Use server サーバを使う @@ -6041,11 +6162,23 @@ To connect, please ask your contact to create another connection link and check With reduced battery usage. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + No comment provided by engineer. + Wrong database passphrase データベースのパスフレーズが違います No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + snd error text + Wrong passphrase! パスフレーズが違います! @@ -7124,6 +7257,12 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 send direct message No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address profile update event chat item @@ -7160,10 +7299,18 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 不明 connection info + + unknown relays + No comment provided by engineer. + unknown status No comment provided by engineer. + + unprotected + No comment provided by engineer. + updated group profile グループプロフィールを更新しました @@ -7227,6 +7374,10 @@ SimpleX サーバーはあなたのプロファイルを参照できません。 time unit + + when IP hidden + No comment provided by engineer. + yes はい 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 c5a9ba4ea8..e4ee887f4b 100644 --- a/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff +++ b/apps/ios/SimpleX Localizations/nl.xcloc/Localized Contents/nl.xliff @@ -713,6 +713,11 @@ Sta verdwijnende berichten alleen toe als uw contact dit toestaat. No comment provided by engineer. + + Allow downgrade + Downgraden toestaan + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Sta het onomkeerbaar verwijderen van berichten alleen toe als uw contact dit toestaat. (24 uur) @@ -808,6 +813,11 @@ Al lid van de groep! No comment provided by engineer. + + Always use private routing. + Gebruik altijd privéroutering. + No comment provided by engineer. + Always use relay Altijd relay gebruiken @@ -1088,6 +1098,11 @@ Kan bestand niet ontvangen No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + Capaciteit overschreden - ontvanger heeft eerder verzonden berichten niet ontvangen. + snd error text + Cellular Mobiel @@ -1284,6 +1299,11 @@ Bevestig database upgrades No comment provided by engineer. + + Confirm files from unknown servers. + Bevestig bestanden van onbekende servers. + No comment provided by engineer. + Confirm network settings Bevestig netwerk instellingen @@ -1701,6 +1721,10 @@ Dit is uw eigen eenmalige link! De database wordt gemigreerd wanneer de app opnieuw wordt opgestart No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized Gedecentraliseerd @@ -1948,6 +1972,11 @@ Dit kan niet ongedaan gemaakt worden! Desktop apparaten No comment provided by engineer. + + Destination server error: %@ + Bestemmingsserverfout: %@ + snd error text + Develop Ontwikkelen @@ -2053,11 +2082,21 @@ Dit kan niet ongedaan gemaakt worden! Ontdek via het lokale netwerk No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + Stuur GEEN berichten rechtstreeks, zelfs als uw of de bestemmingsserver geen privéroutering ondersteunt. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. Gebruik SimpleX NIET voor noodoproepen. No comment provided by engineer. + + Do NOT use private routing. + Gebruik GEEN privéroutering. + No comment provided by engineer. + Do it later Doe het later @@ -2621,7 +2660,7 @@ Dit kan niet ongedaan gemaakt worden! Error: %@ Fout: %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2713,6 +2752,11 @@ Dit kan niet ongedaan gemaakt worden! Bestand: %@ No comment provided by engineer. + + Files + Bestanden + No comment provided by engineer. + Files & media Bestanden en media @@ -2818,6 +2862,20 @@ Dit kan niet ongedaan gemaakt worden! Doorgestuurd vanuit No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + Doorstuurserver: %1$@ +Bestemmingsserverfout: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + Doorstuurserver: %1$@ +Fout: %2$@ + snd error text + Found desktop Desktop gevonden @@ -3633,11 +3691,20 @@ Dit is jouw link voor groep %@! Ontvangst bevestiging voor berichten! No comment provided by engineer. + + Message delivery warning + Waarschuwing voor berichtbezorging + item status text + Message draft Concept bericht No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions Reacties op berichten @@ -3653,6 +3720,16 @@ Dit is jouw link voor groep %@! Reacties op berichten zijn verboden in deze groep. No comment provided by engineer. + + Message routing fallback + Terugval op berichtroutering + No comment provided by engineer. + + + Message routing mode + Berichtrouteringsmodus + No comment provided by engineer. + Message source remains private. Berichtbron blijft privé. @@ -3783,11 +3860,6 @@ Dit is jouw link voor groep %@! Hoogstwaarschijnlijk is deze verbinding verwijderd. item status description - - Most likely this contact has deleted the connection with you. - Hoogstwaarschijnlijk heeft dit contact de verbinding met jou verwijderd. - No comment provided by engineer. - Multiple chat profiles Meerdere chat profielen @@ -3818,6 +3890,11 @@ Dit is jouw link voor groep %@! Netwerkverbinding No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + Netwerkproblemen - bericht is verlopen na vele pogingen om het te verzenden. + snd error text + Network management Netwerkbeheer @@ -4359,11 +4436,26 @@ Fout: %@ Privé bestandsnamen No comment provided by engineer. + + Private message routing + Routering van privéberichten + No comment provided by engineer. + + + Private message routing 🚀 + Routing van privéberichten🚀 + No comment provided by engineer. + Private notes Privé notities name of notes to self + + Private routing + Privéroutering + No comment provided by engineer. + Profile and server connections Profiel- en serververbindingen @@ -4444,11 +4536,23 @@ Fout: %@ Verbieden het verzenden van spraak berichten. No comment provided by engineer. + + Protect IP address + Bescherm het IP-adres + No comment provided by engineer. + Protect app screen App scherm verbergen No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + Bescherm uw IP-adres tegen de berichtenrelais die door uw contacten zijn gekozen. +Schakel dit in in *Netwerk en servers*-instellingen. + No comment provided by engineer. + Protect your chat profiles with a password! Bescherm je chat profielen met een wachtwoord! @@ -4554,11 +4658,6 @@ Fout: %@ Het ontvangstadres wordt gewijzigd naar een andere server. Adres wijziging wordt voltooid nadat de afzender online is. No comment provided by engineer. - - Receiving concurrency - Gelijktijdig ontvangen - No comment provided by engineer. - Receiving file will be stopped. Het ontvangen van het bestand wordt gestopt. @@ -4794,6 +4893,11 @@ Fout: %@ SMP servers No comment provided by engineer. + + Safely receive files + Veilig bestanden ontvangen + No comment provided by engineer. + Safer groups Veiligere groepen @@ -5019,6 +5123,16 @@ Fout: %@ Stuur een livebericht No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + Stuur berichten rechtstreeks als het IP-adres beschermd is en uw of bestemmingsserver geen privéroutering ondersteunt. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + Stuur berichten rechtstreeks wanneer uw of de doelserver geen privéroutering ondersteunt. + No comment provided by engineer. + Send notifications Meldingen verzenden @@ -5124,6 +5238,11 @@ Fout: %@ Verzonden berichten worden na ingestelde tijd verwijderd. No comment provided by engineer. + + Server address is incompatible with network settings. + Serveradres is niet compatibel met netwerkinstellingen. + srv error text. + Server requires authorization to create queues, check password Server vereist autorisatie om wachtrijen te maken, controleer wachtwoord @@ -5139,6 +5258,11 @@ Fout: %@ Servertest mislukt! No comment provided by engineer. + + Server version is incompatible with network settings. + Serverversie is incompatibel met netwerkinstellingen. + srv error text + Servers Servers @@ -5259,11 +5383,21 @@ Fout: %@ Laat laatste berichten zien No comment provided by engineer. + + Show message status + Toon berichtstatus + No comment provided by engineer. + Show preview Toon voorbeeld No comment provided by engineer. + + Show → on messages sent via private routing. + Toon → bij berichten verzonden via privéroutering. + No comment provided by engineer. + Show: Toon: @@ -5586,6 +5720,11 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. De app kan u op de hoogte stellen wanneer u berichten of contact verzoeken ontvangt - open de instellingen om dit in te schakelen. No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + De app vraagt om downloads van onbekende bestandsservers (behalve .onion) te bevestigen. + No comment provided by engineer. + The attempt to change database passphrase was not completed. De poging om het wachtwoord van de database te wijzigen is niet voltooid. @@ -5771,6 +5910,11 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast. Om de tijdzone te beschermen, gebruiken afbeeldings-/spraakbestanden UTC. No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + Om uw IP-adres te beschermen, gebruikt privéroutering uw SMP-servers om berichten te bezorgen. + No comment provided by engineer. + To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled. @@ -5863,11 +6007,6 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Lid deblokkeren? No comment provided by engineer. - - Unexpected error: %@ - Onverwachte fout: %@ - item status description - Unexpected migration state Onverwachte migratiestatus @@ -5913,6 +6052,11 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc Onbekende fout No comment provided by engineer. + + Unknown servers! + Onbekende servers! + No comment provided by engineer. + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. Schakel de modus Niet storen in om onderbrekingen te voorkomen, tenzij u de iOS-oproepinterface gebruikt. @@ -6060,6 +6204,16 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Alleen lokale meldingen gebruiken? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + Gebruik privéroutering met onbekende servers wanneer het IP-adres niet beveiligd is. + No comment provided by engineer. + + + Use private routing with unknown servers. + Gebruik privéroutering met onbekende servers. + No comment provided by engineer. + Use server Gebruik server @@ -6295,11 +6449,26 @@ Om verbinding te maken, vraagt u uw contact om een andere verbinding link te mak Met verminderd batterijgebruik. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + Zonder Tor of VPN is uw IP-adres zichtbaar voor bestandsservers. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + Zonder Tor of VPN zal uw IP-adres zichtbaar zijn voor deze XFTP-relays: %@. + No comment provided by engineer. + Wrong database passphrase Verkeerd wachtwoord voor de database No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + Verkeerde sleutel of onbekende verbinding - hoogstwaarschijnlijk is deze verbinding verwijderd. + snd error text + Wrong passphrase! Verkeerd wachtwoord! @@ -7415,6 +7584,12 @@ SimpleX servers kunnen uw profiel niet zien. stuur een direct bericht No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address nieuw contactadres instellen @@ -7455,11 +7630,21 @@ SimpleX servers kunnen uw profiel niet zien. onbekend connection info + + unknown relays + onbekende relays + No comment provided by engineer. + unknown status onbekende status No comment provided by engineer. + + unprotected + onbeschermd + No comment provided by engineer. + updated group profile bijgewerkt groep profiel @@ -7525,6 +7710,11 @@ SimpleX servers kunnen uw profiel niet zien. weken time unit + + when IP hidden + wanneer IP verborgen is + No comment provided by engineer. + yes Ja 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 d71bd5843a..3a165f8c43 100644 --- a/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff +++ b/apps/ios/SimpleX Localizations/pl.xcloc/Localized Contents/pl.xliff @@ -713,6 +713,11 @@ Zezwól na znikające wiadomości tylko wtedy, gdy Twój kontakt Ci na to pozwoli. No comment provided by engineer. + + Allow downgrade + Zezwól na obniżenie wersji + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Zezwalaj na nieodwracalne usuwanie wiadomości tylko wtedy, gdy Twój kontakt Ci na to pozwoli. (24 godziny) @@ -808,6 +813,11 @@ Już dołączono do grupy! No comment provided by engineer. + + Always use private routing. + Zawsze używaj prywatnego trasowania. + No comment provided by engineer. + Always use relay Zawsze używaj przekaźnika @@ -1088,6 +1098,11 @@ Nie można odebrać pliku No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + Przekroczono pojemność - odbiorca nie otrzymał wcześniej wysłanych wiadomości. + snd error text + Cellular Sieć komórkowa @@ -1284,6 +1299,11 @@ Potwierdź aktualizacje bazy danych No comment provided by engineer. + + Confirm files from unknown servers. + Potwierdzaj pliki z nieznanych serwerów. + No comment provided by engineer. + Confirm network settings Potwierdź ustawienia sieciowe @@ -1701,6 +1721,10 @@ To jest twój jednorazowy link! Baza danych zostanie zmigrowana po ponownym uruchomieniu aplikacji No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized Zdecentralizowane @@ -1948,6 +1972,11 @@ To nie może być cofnięte! Urządzenia komputerowe No comment provided by engineer. + + Destination server error: %@ + Błąd docelowego serwera: %@ + snd error text + Develop Deweloperskie @@ -2053,11 +2082,21 @@ To nie może być cofnięte! Odkryj przez sieć lokalną No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + NIE wysyłaj wiadomości bezpośrednio, nawet jeśli serwer docelowy nie obsługuje prywatnego trasowania. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. NIE używaj SimpleX do połączeń alarmowych. No comment provided by engineer. + + Do NOT use private routing. + NIE używaj prywatnego trasowania. + No comment provided by engineer. + Do it later Zrób to później @@ -2621,7 +2660,7 @@ To nie może być cofnięte! Error: %@ Błąd: %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2713,6 +2752,11 @@ To nie może być cofnięte! Plik: %@ No comment provided by engineer. + + Files + Pliki + No comment provided by engineer. + Files & media Pliki i media @@ -2818,6 +2862,20 @@ To nie może być cofnięte! Przekazane dalej od No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + Serwer przekazujący: %1$@ +Błąd serwera docelowego: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + Serwer przekazujący: %1$@ +Błąd: %2$@ + snd error text + Found desktop Znaleziono komputer @@ -3633,11 +3691,20 @@ To jest twój link do grupy %@! Potwierdzenia dostarczenia wiadomości! No comment provided by engineer. + + Message delivery warning + Ostrzeżenie dostarczenia wiadomości + item status text + Message draft Wersja robocza wiadomości No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions Reakcje wiadomości @@ -3653,6 +3720,16 @@ To jest twój link do grupy %@! Reakcje wiadomości są zabronione w tej grupie. No comment provided by engineer. + + Message routing fallback + Rezerwowe trasowania wiadomości + No comment provided by engineer. + + + Message routing mode + Tryb trasowania wiadomości + No comment provided by engineer. + Message source remains private. Źródło wiadomości pozostaje prywatne. @@ -3783,11 +3860,6 @@ To jest twój link do grupy %@! Najprawdopodobniej to połączenie jest usunięte. item status description - - Most likely this contact has deleted the connection with you. - Najprawdopodobniej ten kontakt usunął połączenie z Tobą. - No comment provided by engineer. - Multiple chat profiles Wiele profili czatu @@ -3818,6 +3890,11 @@ To jest twój link do grupy %@! Połączenie z siecią No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + Błąd sieciowy - wiadomość wygasła po wielu próbach wysłania jej. + snd error text + Network management Zarządzenie sieciowe @@ -4359,11 +4436,26 @@ Błąd: %@ Prywatne nazwy plików No comment provided by engineer. + + Private message routing + Trasowanie prywatnych wiadomości + No comment provided by engineer. + + + Private message routing 🚀 + Trasowanie prywatnych wiadomości🚀 + No comment provided by engineer. + Private notes Prywatne notatki name of notes to self + + Private routing + Prywatne trasowanie + No comment provided by engineer. + Profile and server connections Profil i połączenia z serwerem @@ -4444,11 +4536,23 @@ Błąd: %@ Zabroń wysyłania wiadomości głosowych. No comment provided by engineer. + + Protect IP address + Chroń adres IP + No comment provided by engineer. + Protect app screen Chroń ekran aplikacji No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + Chroni Twój adres IP przed przekaźnikami wiadomości wybranych przez Twoje kontakty. +Włącz w ustawianiach *Sieć i serwery* . + No comment provided by engineer. + Protect your chat profiles with a password! Chroń swoje profile czatu hasłem! @@ -4554,11 +4658,6 @@ Błąd: %@ Adres odbiorczy zostanie zmieniony na inny serwer. Zmiana adresu zostanie zakończona gdy nadawca będzie online. No comment provided by engineer. - - Receiving concurrency - Konkurencyjne odbieranie - No comment provided by engineer. - Receiving file will be stopped. Odbieranie pliku zostanie przerwane. @@ -4794,6 +4893,11 @@ Błąd: %@ Serwery SMP No comment provided by engineer. + + Safely receive files + Bezpiecznie otrzymuj pliki + No comment provided by engineer. + Safer groups Bezpieczniejsze grupy @@ -5019,6 +5123,16 @@ Błąd: %@ Wyślij wiadomość na żywo No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + Wysyłaj wiadomości bezpośrednio, gdy adres IP jest chroniony i Twój lub docelowy serwer nie obsługuje prywatnego trasowania. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + Wysyłaj wiadomości bezpośrednio, gdy Twój lub docelowy serwer nie obsługuje prywatnego trasowania. + No comment provided by engineer. + Send notifications Wyślij powiadomienia @@ -5124,6 +5238,11 @@ Błąd: %@ Wysłane wiadomości zostaną usunięte po ustawionym czasie. No comment provided by engineer. + + Server address is incompatible with network settings. + Adres serwera jest niekompatybilny z ustawieniami sieciowymi. + srv error text. + Server requires authorization to create queues, check password Serwer wymaga autoryzacji do tworzenia kolejek, sprawdź hasło @@ -5139,6 +5258,11 @@ Błąd: %@ Test serwera nie powiódł się! No comment provided by engineer. + + Server version is incompatible with network settings. + Wersja serwera jest niekompatybilna z ustawieniami sieciowymi. + srv error text + Servers Serwery @@ -5259,11 +5383,21 @@ Błąd: %@ Pokaż ostatnie wiadomości No comment provided by engineer. + + Show message status + Pokaż status wiadomości + No comment provided by engineer. + Show preview Pokaż podgląd No comment provided by engineer. + + Show → on messages sent via private routing. + Pokaż → na wiadomościach wysłanych przez prywatne trasowanie. + No comment provided by engineer. + Show: Pokaż: @@ -5586,6 +5720,11 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Aplikacja może powiadamiać Cię, gdy otrzymujesz wiadomości lub prośby o kontakt — otwórz ustawienia, aby włączyć. No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + Aplikacja zapyta o potwierdzenie pobierania od nieznanych serwerów plików (poza .onion). + No comment provided by engineer. + The attempt to change database passphrase was not completed. Próba zmiany hasła bazy danych nie została zakończona. @@ -5771,6 +5910,11 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom Aby chronić strefę czasową, pliki obrazów/głosów używają UTC. No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + Aby chronić Twój adres IP, prywatne trasowanie używa Twoich serwerów SMP, aby dostarczyć wiadomości. + No comment provided by engineer. + To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled. @@ -5863,11 +6007,6 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.Odblokować członka? No comment provided by engineer. - - Unexpected error: %@ - Nieoczekiwany błąd: %@ - item status description - Unexpected migration state Nieoczekiwany stan migracji @@ -5913,6 +6052,11 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.Nieznany błąd No comment provided by engineer. + + Unknown servers! + Nieznane serwery! + No comment provided by engineer. + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. O ile nie korzystasz z interfejsu połączeń systemu iOS, włącz tryb Nie przeszkadzać, aby uniknąć przerywania. @@ -6060,6 +6204,16 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Używać tylko lokalnych powiadomień? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + Używaj prywatnego trasowania z nieznanymi serwerami, gdy adres IP nie jest chroniony. + No comment provided by engineer. + + + Use private routing with unknown servers. + Używaj prywatnego trasowania z nieznanymi serwerami. + No comment provided by engineer. + Use server Użyj serwera @@ -6295,11 +6449,26 @@ Aby się połączyć, poproś Twój kontakt o utworzenie kolejnego linku połąc Ze zmniejszonym zużyciem baterii. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + Bez Tor lub VPN, Twój adres IP będzie widoczny do serwerów plików. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + Bez Tor lub VPN, Twój adres IP będzie widoczny dla tych przekaźników XFTP: %@. + No comment provided by engineer. + Wrong database passphrase Nieprawidłowe hasło bazy danych No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + Zły klucz lub nieznane połączenie - najprawdopodobniej to połączenie jest usunięte. + snd error text + Wrong passphrase! Nieprawidłowe hasło! @@ -7415,6 +7584,12 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. wyślij wiadomość bezpośrednią No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address ustaw nowy adres kontaktu @@ -7455,11 +7630,21 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. nieznany connection info + + unknown relays + nieznane przekaźniki + No comment provided by engineer. + unknown status nieznany status No comment provided by engineer. + + unprotected + niezabezpieczony + No comment provided by engineer. + updated group profile zaktualizowano profil grupy @@ -7525,6 +7710,11 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu. tygodnie time unit + + when IP hidden + gdy IP ukryty + No comment provided by engineer. + yes tak 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 ba1ac7a929..55c22354a9 100644 --- a/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff +++ b/apps/ios/SimpleX Localizations/ru.xcloc/Localized Contents/ru.xliff @@ -713,6 +713,11 @@ Разрешить исчезающие сообщения, только если Ваш контакт разрешает их Вам. No comment provided by engineer. + + Allow downgrade + Разрешить прямую доставку + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Разрешить необратимое удаление сообщений, только если Ваш контакт разрешает это Вам. (24 часа) @@ -808,6 +813,11 @@ Вступление в группу уже начато! No comment provided by engineer. + + Always use private routing. + Всегда использовать конфиденциальную доставку. + No comment provided by engineer. + Always use relay Всегда соединяться через relay @@ -1088,6 +1098,11 @@ Невозможно получить файл No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + Превышено количество сообщений - предыдущие сообщения не доставлены. + snd error text + Cellular Мобильная сеть @@ -1284,6 +1299,11 @@ Подтвердить обновление базы данных No comment provided by engineer. + + Confirm files from unknown servers. + Подтверждать файлы с неизвестных серверов. + No comment provided by engineer. + Confirm network settings Подтвердите настройки сети @@ -1701,6 +1721,10 @@ This is your own one-time link! Данные чата будут мигрированы при перезапуске No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized Децентрализованный @@ -1948,6 +1972,11 @@ This cannot be undone! Компьютеры No comment provided by engineer. + + Destination server error: %@ + Ошибка сервера получателя: %@ + snd error text + Develop Для разработчиков @@ -2053,11 +2082,21 @@ This cannot be undone! Обнаружение по локальной сети No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + Не отправлять сообщения напрямую, даже если сервер получателя не поддерживает конфиденциальную доставку. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. Не используйте SimpleX для экстренных звонков. No comment provided by engineer. + + Do NOT use private routing. + Не использовать конфиденциальную доставку. + No comment provided by engineer. + Do it later Отложить @@ -2621,7 +2660,7 @@ This cannot be undone! Error: %@ Ошибка: %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2713,6 +2752,11 @@ This cannot be undone! Файл: %@ No comment provided by engineer. + + Files + Файлы + No comment provided by engineer. + Files & media Файлы и медиа @@ -2818,6 +2862,20 @@ This cannot be undone! Переслано из No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + Пересылающий сервер: %1$@ +Ошибка сервера получателя: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + Пересылающий сервер: %1$@ +Ошибка: %2$@ + snd error text + Found desktop Компьютер найден @@ -3633,11 +3691,20 @@ This is your link for group %@! Отчеты о доставке сообщений! No comment provided by engineer. + + Message delivery warning + Предупреждение доставки сообщения + item status text + Message draft Черновик сообщения No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions Реакции на сообщения @@ -3653,6 +3720,16 @@ This is your link for group %@! Реакции на сообщения запрещены в этой группе. No comment provided by engineer. + + Message routing fallback + Прямая доставка сообщений + No comment provided by engineer. + + + Message routing mode + Режим доставки сообщений + No comment provided by engineer. + Message source remains private. Источник сообщения остаётся конфиденциальным. @@ -3783,11 +3860,6 @@ This is your link for group %@! Скорее всего, соединение удалено. item status description - - Most likely this contact has deleted the connection with you. - Скорее всего, этот контакт удалил соединение с Вами. - No comment provided by engineer. - Multiple chat profiles Много профилей чата @@ -3818,6 +3890,11 @@ This is your link for group %@! Интернет-соединение No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + Ошибка сети - сообщение не было отправлено после многократных попыток. + snd error text + Network management Статус сети @@ -4359,11 +4436,26 @@ Error: %@ Защищенные имена файлов No comment provided by engineer. + + Private message routing + Конфиденциальная доставка сообщений + No comment provided by engineer. + + + Private message routing 🚀 + Конфиденциальная доставка сообщений 🚀 + No comment provided by engineer. + Private notes Личные заметки name of notes to self + + Private routing + Конфиденциальная доставка + No comment provided by engineer. + Profile and server connections Профиль и соединения на сервере @@ -4444,11 +4536,23 @@ Error: %@ Запретить отправлять голосовые сообщений. No comment provided by engineer. + + Protect IP address + Защитить IP адрес + No comment provided by engineer. + Protect app screen Защитить экран приложения No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + Защитите ваш IP адрес от серверов сообщений, выбранных Вашими контактами. +Включите в настройках *Сеть и серверы*. + No comment provided by engineer. + Protect your chat profiles with a password! Защитите Ваши профили чата паролем! @@ -4554,11 +4658,6 @@ Error: %@ Адрес получения сообщений будет перемещён на другой сервер. Изменение адреса завершится после того как отправитель будет онлайн. No comment provided by engineer. - - Receiving concurrency - Одновременный приём - No comment provided by engineer. - Receiving file will be stopped. Приём файла будет прекращён. @@ -4794,6 +4893,11 @@ Error: %@ SMP серверы No comment provided by engineer. + + Safely receive files + Получайте файлы безопасно + No comment provided by engineer. + Safer groups Более безопасные группы @@ -5019,6 +5123,16 @@ Error: %@ Отправить живое сообщение No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + Отправлять сообщения напрямую, когда IP адрес защищен, и Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + Отправлять сообщения напрямую, когда Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку. + No comment provided by engineer. + Send notifications Отправлять уведомления @@ -5124,6 +5238,11 @@ Error: %@ Отправленные сообщения будут удалены через заданное время. No comment provided by engineer. + + Server address is incompatible with network settings. + Адрес сервера несовместим с настройками сети. + srv error text. + Server requires authorization to create queues, check password Сервер требует авторизации для создания очередей, проверьте пароль @@ -5139,6 +5258,11 @@ Error: %@ Ошибка теста сервера! No comment provided by engineer. + + Server version is incompatible with network settings. + Версия сервера несовместима с настройками сети. + srv error text + Servers Серверы @@ -5259,11 +5383,21 @@ Error: %@ Показывать последние сообщения No comment provided by engineer. + + Show message status + Показать статус сообщения + No comment provided by engineer. + Show preview Показывать уведомления No comment provided by engineer. + + Show → on messages sent via private routing. + Показать → на сообщениях доставленных конфиденциально. + No comment provided by engineer. + Show: Показать: @@ -5586,6 +5720,11 @@ It can happen because of some bug or when the connection is compromised.Приложение может посылать Вам уведомления о сообщениях и запросах на соединение - уведомления можно включить в Настройках. No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + Приложение будет запрашивать подтверждение загрузки с неизвестных серверов (за исключением .onion адресов). + No comment provided by engineer. + The attempt to change database passphrase was not completed. Попытка поменять пароль базы данных не была завершена. @@ -5771,6 +5910,11 @@ It can happen because of some bug or when the connection is compromised.Чтобы защитить Ваш часовой пояс, файлы картинок и голосовых сообщений используют UTC. No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + Чтобы защитить ваш IP адрес, приложение использует Ваши SMP серверы для конфиденциальной доставки сообщений. + No comment provided by engineer. + To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled. @@ -5863,11 +6007,6 @@ You will be prompted to complete authentication before this feature is enabled.< Разблокировать члена группы? No comment provided by engineer. - - Unexpected error: %@ - Неожиданная ошибка: %@ - item status description - Unexpected migration state Неожиданная ошибка при перемещении данных чата @@ -5913,6 +6052,11 @@ You will be prompted to complete authentication before this feature is enabled.< Неизвестная ошибка No comment provided by engineer. + + Unknown servers! + Неизвестные серверы! + No comment provided by engineer. + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. Если Вы не используете интерфейс iOS, включите режим Не отвлекать, чтобы звонок не прерывался. @@ -6060,6 +6204,16 @@ To connect, please ask your contact to create another connection link and check Использовать только локальные нотификации? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + Использовать конфиденциальную доставку с неизвестными серверами, когда IP адрес не защищен. + No comment provided by engineer. + + + Use private routing with unknown servers. + Использовать конфиденциальную доставку с неизвестными серверами. + No comment provided by engineer. + Use server Использовать сервер @@ -6295,11 +6449,26 @@ To connect, please ask your contact to create another connection link and check С уменьшенным потреблением батареи. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + Без Тора или ВПН, Ваш IP адрес будет доступен серверам файлов. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + Без Тора или ВПН, Ваш IP адрес будет доступен этим серверам файлов: %@. + No comment provided by engineer. + Wrong database passphrase Неправильный пароль базы данных No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + Неверный ключ или неизвестное соединение - скорее всего, это соединение удалено. + snd error text + Wrong passphrase! Неправильный пароль! @@ -7415,6 +7584,12 @@ SimpleX серверы не могут получить доступ к Ваше отправьте сообщение No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address установлен новый адрес контакта @@ -7455,11 +7630,21 @@ SimpleX серверы не могут получить доступ к Ваше неизвестно connection info + + unknown relays + неизвестные серверы + No comment provided by engineer. + unknown status неизвестный статус No comment provided by engineer. + + unprotected + незащищённый + No comment provided by engineer. + updated group profile обновил(а) профиль группы @@ -7525,6 +7710,11 @@ SimpleX серверы не могут получить доступ к Ваше недель time unit + + when IP hidden + когда IP защищен + No comment provided by engineer. + yes да 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 1f62fad60f..4f4f41aa38 100644 --- a/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff +++ b/apps/ios/SimpleX Localizations/th.xcloc/Localized Contents/th.xliff @@ -677,6 +677,10 @@ อนุญาตให้ข้อความที่หายไปเฉพาะในกรณีที่ผู้ติดต่อของคุณอนุญาตเท่านั้น. No comment provided by engineer. + + Allow downgrade + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) อนุญาตให้ลบข้อความแบบถาวรเฉพาะในกรณีที่ผู้ติดต่อของคุณอนุญาตให้คุณเท่านั้น @@ -769,6 +773,10 @@ Already joining the group! No comment provided by engineer. + + Always use private routing. + No comment provided by engineer. + Always use relay ใช้รีเลย์เสมอ @@ -1032,6 +1040,10 @@ ไม่สามารถรับไฟล์ได้ No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + snd error text + Cellular No comment provided by engineer. @@ -1223,6 +1235,10 @@ ยืนยันการอัพเกรดฐานข้อมูล No comment provided by engineer. + + Confirm files from unknown servers. + No comment provided by engineer. + Confirm network settings No comment provided by engineer. @@ -1613,6 +1629,10 @@ This is your own one-time link! ระบบจะย้ายฐานข้อมูลเมื่อแอปรีสตาร์ท No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized กระจายอำนาจแล้ว @@ -1851,6 +1871,10 @@ This cannot be undone! Desktop devices No comment provided by engineer. + + Destination server error: %@ + snd error text + Develop พัฒนา @@ -1953,11 +1977,19 @@ This cannot be undone! Discover via local network No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. อย่าใช้ SimpleX สําหรับการโทรฉุกเฉิน No comment provided by engineer. + + Do NOT use private routing. + No comment provided by engineer. + Do it later ทำในภายหลัง @@ -2494,7 +2526,7 @@ This cannot be undone! Error: %@ ข้อผิดพลาด: % @ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2583,6 +2615,10 @@ This cannot be undone! ไฟล์: % @ No comment provided by engineer. + + Files + No comment provided by engineer. + Files & media ไฟล์และสื่อ @@ -2681,6 +2717,16 @@ This cannot be undone! Forwarded from No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + snd error text + Found desktop No comment provided by engineer. @@ -3463,11 +3509,19 @@ This is your link for group %@! ใบเสร็จการส่งข้อความ! No comment provided by engineer. + + Message delivery warning + item status text + Message draft ร่างข้อความ No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions ปฏิกิริยาของข้อความ @@ -3483,6 +3537,14 @@ This is your link for group %@! ปฏิกิริยาบนข้อความเป็นสิ่งต้องห้ามในกลุ่มนี้ No comment provided by engineer. + + Message routing fallback + No comment provided by engineer. + + + Message routing mode + No comment provided by engineer. + Message source remains private. No comment provided by engineer. @@ -3599,11 +3661,6 @@ This is your link for group %@! Most likely this connection is deleted. item status description - - Most likely this contact has deleted the connection with you. - เป็นไปได้มากว่าผู้ติดต่อนี้ได้ลบการเชื่อมต่อกับคุณ - No comment provided by engineer. - Multiple chat profiles โปรไฟล์การแชทหลายรายการ @@ -3633,6 +3690,10 @@ This is your link for group %@! Network connection No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + snd error text + Network management No comment provided by engineer. @@ -4150,10 +4211,22 @@ Error: %@ ชื่อไฟล์ส่วนตัว No comment provided by engineer. + + Private message routing + No comment provided by engineer. + + + Private message routing 🚀 + No comment provided by engineer. + Private notes name of notes to self + + Private routing + No comment provided by engineer. + Profile and server connections การเชื่อมต่อโปรไฟล์และเซิร์ฟเวอร์ @@ -4230,11 +4303,20 @@ Error: %@ ห้ามส่งข้อความเสียง No comment provided by engineer. + + Protect IP address + No comment provided by engineer. + Protect app screen ปกป้องหน้าจอแอป No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + No comment provided by engineer. + Protect your chat profiles with a password! ปกป้องโปรไฟล์การแชทของคุณด้วยรหัสผ่าน! @@ -4336,10 +4418,6 @@ Error: %@ ที่อยู่ผู้รับจะถูกเปลี่ยนเป็นเซิร์ฟเวอร์อื่น การเปลี่ยนแปลงที่อยู่จะเสร็จสมบูรณ์หลังจากที่ผู้ส่งออนไลน์ No comment provided by engineer. - - Receiving concurrency - No comment provided by engineer. - Receiving file will be stopped. การรับไฟล์จะหยุดลง @@ -4566,6 +4644,10 @@ Error: %@ เซิร์ฟเวอร์ SMP No comment provided by engineer. + + Safely receive files + No comment provided by engineer. + Safer groups No comment provided by engineer. @@ -4783,6 +4865,14 @@ Error: %@ ส่งข้อความสด No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + No comment provided by engineer. + Send notifications ส่งการแจ้งเตือน @@ -4885,6 +4975,10 @@ Error: %@ ข้อความที่ส่งจะถูกลบหลังเกินเวลาที่กําหนด No comment provided by engineer. + + Server address is incompatible with network settings. + srv error text. + Server requires authorization to create queues, check password เซิร์ฟเวอร์ต้องการการอนุญาตในการสร้างคิว โปรดตรวจสอบรหัสผ่าน @@ -4900,6 +4994,10 @@ Error: %@ การทดสอบเซิร์ฟเวอร์ล้มเหลว! No comment provided by engineer. + + Server version is incompatible with network settings. + srv error text + Servers เซิร์ฟเวอร์ @@ -5014,11 +5112,19 @@ Error: %@ Show last messages No comment provided by engineer. + + Show message status + No comment provided by engineer. + Show preview แสดงตัวอย่าง No comment provided by engineer. + + Show → on messages sent via private routing. + No comment provided by engineer. + Show: แสดง: @@ -5331,6 +5437,10 @@ It can happen because of some bug or when the connection is compromised.แอปสามารถแจ้งให้คุณทราบเมื่อคุณได้รับข้อความหรือคำขอติดต่อ - โปรดเปิดการตั้งค่าเพื่อเปิดใช้งาน No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + No comment provided by engineer. + The attempt to change database passphrase was not completed. ความพยายามในการเปลี่ยนรหัสผ่านของฐานข้อมูลไม่เสร็จสมบูรณ์ @@ -5505,6 +5615,10 @@ It can happen because of some bug or when the connection is compromised.ไฟล์ภาพ/เสียงใช้ UTC เพื่อป้องกันเขตเวลา No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + 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. @@ -5590,11 +5704,6 @@ You will be prompted to complete authentication before this feature is enabled.< Unblock member? No comment provided by engineer. - - Unexpected error: %@ - ข้อผิดพลาดที่ไม่คาดคิด: %@ - item status description - Unexpected migration state สถานะการย้ายข้อมูลที่ไม่คาดคิด @@ -5640,6 +5749,10 @@ You will be prompted to complete authentication before this feature is enabled.< ข้อผิดพลาดที่ไม่รู้จัก No comment provided by engineer. + + Unknown servers! + No comment provided by engineer. + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. ยกเว้นกรณีที่คุณใช้อินเทอร์เฟซการโทรของ iOS ให้เปิดใช้งานโหมดห้ามรบกวนเพื่อหลีกเลี่ยงการรบกวน @@ -5778,6 +5891,14 @@ To connect, please ask your contact to create another connection link and check Use only local notifications? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + No comment provided by engineer. + + + Use private routing with unknown servers. + No comment provided by engineer. + Use server ใช้เซิร์ฟเวอร์ @@ -5995,11 +6116,23 @@ To connect, please ask your contact to create another connection link and check With reduced battery usage. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + No comment provided by engineer. + Wrong database passphrase รหัสผ่านฐานข้อมูลไม่ถูกต้อง No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + snd error text + Wrong passphrase! รหัสผ่านผิด! @@ -7074,6 +7207,12 @@ SimpleX servers cannot see your profile. send direct message No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address profile update event chat item @@ -7110,10 +7249,18 @@ SimpleX servers cannot see your profile. ไม่ทราบ connection info + + unknown relays + No comment provided by engineer. + unknown status No comment provided by engineer. + + unprotected + No comment provided by engineer. + updated group profile อัปเดตโปรไฟล์กลุ่มแล้ว @@ -7177,6 +7324,10 @@ SimpleX servers cannot see your profile. สัปดาห์ time unit + + when IP hidden + No comment provided by engineer. + yes ใช่ 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 df5076fb07..4a6418c596 100644 --- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff +++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff @@ -445,7 +445,7 @@ 0s - 0 saniye + 0sn No comment provided by engineer. @@ -695,7 +695,7 @@ 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. + Tüm kişileriniz, konuşmalarınız ve dosyalarınız güvenli bir şekilde şifrelenir ve yapılandırılmış XFTP yönlendiricilerine parçalar halinde yüklenir. No comment provided by engineer. @@ -713,6 +713,11 @@ Eğer kişide izin verirse kaybolan mesajlara izin ver. No comment provided by engineer. + + Allow downgrade + Sürüm düşürmeye 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) @@ -730,7 +735,7 @@ Allow sending direct messages to members. - Üyelere direkt mesaj göndermeye izin ver. + Üyelere doğrudan mesaj göndermeye izin ver. No comment provided by engineer. @@ -808,6 +813,11 @@ Zaten gruba bağlanılıyor! No comment provided by engineer. + + Always use private routing. + Her zaman gizli yönlendirme kullan. + No comment provided by engineer. + Always use relay Her zaman yönlendirici kullan @@ -1088,6 +1098,11 @@ Dosya alınamıyor No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + Kapasite aşıldı - alıcı önceden gönderilen mesajları almadı. + snd error text + Cellular Hücresel Veri @@ -1284,6 +1299,11 @@ Veritabanı geliştirmelerini onayla No comment provided by engineer. + + Confirm files from unknown servers. + Bilinmeyen sunuculardan gelen dosyaları onayla. + No comment provided by engineer. + Confirm network settings Ağ ayarlarını onaylayın @@ -1701,6 +1721,10 @@ Bu senin kendi tek kullanımlık bağlantın! Uygulama yeniden başlatıldığında veritabanı taşınacaktır No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized Merkezi Olmayan @@ -1948,6 +1972,11 @@ Bu geri alınamaz! Bilgisayar cihazları No comment provided by engineer. + + Destination server error: %@ + Hedef sunucu hatası: %@ + snd error text + Develop Geliştir @@ -2053,11 +2082,21 @@ Bu geri alınamaz! Yerel ağ aracılığıyla keşfet No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + Sizin veya hedef sunucunun özel yönlendirmeyi desteklememesi durumunda bile mesajları doğrudan GÖNDERMEYİN. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. Acil aramalar için SimpleX'i KULLANMAYIN. No comment provided by engineer. + + Do NOT use private routing. + Gizli yönlendirmeyi KULLANMA. + No comment provided by engineer. + Do it later Sonra yap @@ -2621,7 +2660,7 @@ Bu geri alınamaz! Error: %@ Hata: %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2713,6 +2752,11 @@ Bu geri alınamaz! Dosya: %@ No comment provided by engineer. + + Files + Dosyalar + No comment provided by engineer. + Files & media Dosyalar & medya @@ -2818,6 +2862,20 @@ Bu geri alınamaz! Şuradan iletildi No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + Yönlendirme sunucusu: %1$@ +Hedef sunucu hatası: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + Yönlendirme sunucusu: %1$@ +Hata: %2$@ + snd error text + Found desktop Bilgisayar bulundu @@ -3633,11 +3691,20 @@ Bu senin grup için bağlantın %@! Mesaj alındı bilgisi! No comment provided by engineer. + + Message delivery warning + Mesaj iletimi uyarısı + item status text + Message draft Mesaj taslağı No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions Mesaj tepkileri @@ -3653,6 +3720,16 @@ Bu senin grup için bağlantın %@! Mesaj tepkileri bu grupta yasaklandı. No comment provided by engineer. + + Message routing fallback + Mesaj yönlendirme yedeklemesi + No comment provided by engineer. + + + Message routing mode + Mesaj yönlendirme modu + No comment provided by engineer. + Message source remains private. Mesaj kaynağı gizli kalır. @@ -3783,11 +3860,6 @@ Bu senin grup için bağlantın %@! Büyük ihtimalle bu bağlantı silinmiş. item status description - - Most likely this contact has deleted the connection with you. - Büyük ihtimalle bu kişi seninle bağlantını sildi. - No comment provided by engineer. - Multiple chat profiles Çoklu sohbet profili @@ -3818,6 +3890,11 @@ Bu senin grup için bağlantın %@! Ağ bağlantısı No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + Ağ sorunları - birçok gönderme denemesinden sonra mesajın süresi doldu. + snd error text + Network management Ağ yönetimi @@ -4359,11 +4436,26 @@ Hata: %@ Gizli dosya adları No comment provided by engineer. + + Private message routing + Gizli mesaj yönlendirme + No comment provided by engineer. + + + Private message routing 🚀 + Gizli mesaj yönlendirme 🚀 + No comment provided by engineer. + Private notes Gizli notlar name of notes to self + + Private routing + Gizli yönlendirme + No comment provided by engineer. + Profile and server connections Profil ve sunucu bağlantıları @@ -4426,7 +4518,7 @@ Hata: %@ Prohibit sending direct messages to members. - Geri dönülmez mesaj silme işlemini yasakla. + Üyelere doğrudan mesaj göndermeyi yasakla. No comment provided by engineer. @@ -4444,11 +4536,23 @@ Hata: %@ Sesli mesajların gönderimini yasakla. No comment provided by engineer. + + Protect IP address + IP adresini koru + No comment provided by engineer. + Protect app screen Uygulama ekranını koru No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + IP adresinizi kişileriniz tarafından seçilen mesajlaşma yönlendiricilerinden koruyun. +*Ağ ve sunucular* ayarlarında etkinleştirin. + No comment provided by engineer. + Protect your chat profiles with a password! Bir parolayla birlikte sohbet profillerini koru! @@ -4554,11 +4658,6 @@ Hata: %@ Alıcı adresi farklı bir sunucuya değiştirilecektir. Gönderici çevrimiçi olduktan sonra adres değişikliği tamamlanacaktır. No comment provided by engineer. - - Receiving concurrency - Eşzamanlılık alınıyor - No comment provided by engineer. - Receiving file will be stopped. Dosya alımı durdurulacaktır. @@ -4626,12 +4725,12 @@ Hata: %@ Relay server is only used if necessary. Another party can observe your IP address. - Aktarma sunucusu yalnızca gerekli olduğunda kullanılır. Başka bir taraf IP adresinizi gözlemleyebilir. + Yönlendirici sunucusu yalnızca gerekli olduğunda kullanılır. Başka bir taraf IP adresinizi gözlemleyebilir. No comment provided by engineer. Relay server protects your IP address, but it can observe the duration of the call. - Aktarıcı sunucu IP adresinizi korur, ancak aramanın süresini gözlemleyebilir. + Yönlendirici sunucu IP adresinizi korur, ancak aramanın süresini gözlemleyebilir. No comment provided by engineer. @@ -4794,6 +4893,11 @@ Hata: %@ SMP sunucuları No comment provided by engineer. + + Safely receive files + Dosyaları güvenle alın + No comment provided by engineer. + Safer groups Daha güvenli gruplar @@ -5019,6 +5123,16 @@ Hata: %@ Canlı mesaj gönder No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + IP adresi korumalı olduğunda ve sizin veya hedef sunucunun özel yönlendirmeyi desteklemediği durumlarda mesajları doğrudan gönderin. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + Sizin veya hedef sunucunun özel yönlendirmeyi desteklemediği durumlarda mesajları doğrudan gönderin. + No comment provided by engineer. + Send notifications Bildirimler gönder @@ -5124,6 +5238,11 @@ Hata: %@ Gönderilen mesajlar ayarlanan süreden sonra silinecektir. No comment provided by engineer. + + Server address is incompatible with network settings. + Sunucu adresi ağ ayarlarıyla uyumlu değil. + srv error text. + Server requires authorization to create queues, check password Sunucunun sıra oluşturması için yetki gereklidir, şifreyi kontrol edin @@ -5139,6 +5258,11 @@ Hata: %@ Sunucu testinde hata oluştu! No comment provided by engineer. + + Server version is incompatible with network settings. + Sunucu sürümü ağ ayarlarıyla uyumlu değil. + srv error text + Servers Sunucular @@ -5259,11 +5383,21 @@ Hata: %@ Son mesajları göster No comment provided by engineer. + + Show message status + Mesaj durumunu göster + No comment provided by engineer. + Show preview Ön gösterimi göser No comment provided by engineer. + + Show → on messages sent via private routing. + Gizli yönlendirme yoluyla gönderilen mesajlarda → işaretini göster. + No comment provided by engineer. + Show: Göster: @@ -5586,6 +5720,11 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Uygulama, mesaj veya iletişim isteği aldığınızda sizi bilgilendirebilir - etkinleştirmek için lütfen ayarları açın. No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + Uygulama bilinmeyen dosya sunucularından indirmeleri onaylamanızı isteyecektir (.onion hariç). + No comment provided by engineer. + The attempt to change database passphrase was not completed. Veritabanı parolasını değiştirme girişimi tamamlanmadı. @@ -5771,6 +5910,11 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir. Zaman bölgesini korumak için,fotoğraf/ses dosyaları UTC kullanır. No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + IP adresinizi korumak için,gizli yönlendirme mesajları iletmek için SMP sunucularınızı kullanır. + No comment provided by engineer. + To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled. @@ -5863,11 +6007,6 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Üyenin engeli kaldırılsın mı? No comment provided by engineer. - - Unexpected error: %@ - Beklenmeyen hata: %@ - item status description - Unexpected migration state Beklenmeyen geçiş durumu @@ -5913,6 +6052,11 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec Bilinmeyen hata No comment provided by engineer. + + Unknown servers! + Bilinmeyen sunucular! + 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. @@ -6060,6 +6204,16 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Sadece yerel bildirimler kullanılsın mı? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + IP adresi korunmadığında bilinmeyen sunucularla gizli yönlendirme kullan. + No comment provided by engineer. + + + Use private routing with unknown servers. + Bilinmeyen sunucularla gizli yönlendirme kullan. + No comment provided by engineer. + Use server Sunucu kullan @@ -6295,11 +6449,26 @@ Bağlanmak için lütfen kişinizden başka bir bağlantı oluşturmasını iste Azaltılmış pil kullanımı ile birlikte. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + Tor veya VPN olmadan, IP adresiniz dosya sunucularına görülebilir. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + Tor veya VPN olmadan, IP adresiniz bu XFTP aktarıcıları tarafından görülebilir: %@. + No comment provided by engineer. + Wrong database passphrase Yanlış veritabanı parolası No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + Yanlış anahtar veya bilinmeyen bağlantı - büyük olasılıkla bu bağlantı silinmiştir. + snd error text + Wrong passphrase! Yanlış parola! @@ -7415,6 +7584,12 @@ SimpleX sunucuları profilinizi göremez. doğrudan mesaj gönder No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address yeni kişi adresi ayarla @@ -7455,11 +7630,21 @@ SimpleX sunucuları profilinizi göremez. bilinmeyen connection info + + unknown relays + bilinmeyen yönlendiriciler + No comment provided by engineer. + unknown status bilinmeyen durum No comment provided by engineer. + + unprotected + korumasız + No comment provided by engineer. + updated group profile grup profili güncellendi @@ -7525,6 +7710,11 @@ SimpleX sunucuları profilinizi göremez. haftalar time unit + + when IP hidden + IP gizliyken + No comment provided by engineer. + yes evet 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 1ed1f5ffd6..258c42007a 100644 --- a/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff +++ b/apps/ios/SimpleX Localizations/uk.xcloc/Localized Contents/uk.xliff @@ -713,6 +713,11 @@ Дозволяйте зникати повідомленням, тільки якщо контакт дозволяє вам це робити. No comment provided by engineer. + + Allow downgrade + Дозволити пониження версії + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) Дозволяйте безповоротне видалення повідомлень, тільки якщо контакт дозволяє вам це зробити. (24 години) @@ -745,6 +750,7 @@ Allow to send SimpleX links. + Дозволити надсилати посилання SimpleX. No comment provided by engineer. @@ -807,6 +813,11 @@ Вже приєднуємося до групи! No comment provided by engineer. + + Always use private routing. + Завжди використовуйте приватну маршрутизацію. + No comment provided by engineer. + Always use relay Завжди використовуйте реле @@ -1087,8 +1098,14 @@ Не вдається отримати файл No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + Перевищено ліміт - одержувач не отримав раніше надіслані повідомлення. + snd error text + Cellular + Стільниковий No comment provided by engineer. @@ -1282,6 +1299,11 @@ Підтвердити оновлення бази даних No comment provided by engineer. + + Confirm files from unknown servers. + Підтвердити файли з невідомих серверів. + No comment provided by engineer. + Confirm network settings Підтвердьте налаштування мережі @@ -1699,6 +1721,10 @@ This is your own one-time link! База даних буде перенесена під час перезапуску програми No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized Децентралізований @@ -1946,6 +1972,11 @@ This cannot be undone! Настільні пристрої No comment provided by engineer. + + Destination server error: %@ + Помилка сервера призначення: %@ + snd error text + Develop Розробник @@ -2051,11 +2082,21 @@ This cannot be undone! Відкриття через локальну мережу No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + НЕ надсилайте повідомлення напряму, навіть якщо ваш сервер або сервер призначення не підтримує приватну маршрутизацію. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. НЕ використовуйте SimpleX для екстрених викликів. No comment provided by engineer. + + Do NOT use private routing. + НЕ використовуйте приватну маршрутизацію. + No comment provided by engineer. + Do it later Зробіть це пізніше @@ -2088,6 +2129,7 @@ This cannot be undone! Download + Завантажити chat item action @@ -2202,6 +2244,7 @@ This cannot be undone! Enabled for + Увімкнено для No comment provided by engineer. @@ -2617,7 +2660,7 @@ This cannot be undone! Error: %@ Помилка: %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2709,6 +2752,11 @@ This cannot be undone! Файл: %@ No comment provided by engineer. + + Files + Файли + No comment provided by engineer. + Files & media Файли та медіа @@ -2726,6 +2774,7 @@ This cannot be undone! Files and media not allowed + Файли та медіафайли заборонені No comment provided by engineer. @@ -2795,20 +2844,38 @@ This cannot be undone! Forward + Пересилання chat item action Forward and save messages + Пересилання та збереження повідомлень No comment provided by engineer. Forwarded + Переслано No comment provided by engineer. Forwarded from + Переслано з No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + Сервер переадресації: %1$@ +Помилка сервера призначення: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + Сервер переадресації: %1$@ +Помилка: %2$@ + snd error text + Found desktop Знайдено робочий стіл @@ -2921,6 +2988,7 @@ This cannot be undone! Group members can send SimpleX links. + Учасники групи можуть надсилати посилання SimpleX. No comment provided by engineer. @@ -3165,6 +3233,7 @@ This cannot be undone! In-call sounds + Звуки вхідного дзвінка No comment provided by engineer. @@ -3622,11 +3691,20 @@ This is your link for group %@! Підтвердження доставки повідомлення! No comment provided by engineer. + + Message delivery warning + Попередження про доставку повідомлення + item status text + Message draft Чернетка повідомлення No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions Реакції на повідомлення @@ -3642,8 +3720,19 @@ This is your link for group %@! Реакції на повідомлення в цій групі заборонені. No comment provided by engineer. + + Message routing fallback + Запасний варіант маршрутизації повідомлень + No comment provided by engineer. + + + Message routing mode + Режим маршрутизації повідомлень + No comment provided by engineer. + Message source remains private. + Джерело повідомлення залишається приватним. No comment provided by engineer. @@ -3763,6 +3852,7 @@ This is your link for group %@! More reliable network connection. + Більш надійне з'єднання з мережею. No comment provided by engineer. @@ -3770,11 +3860,6 @@ This is your link for group %@! Швидше за все, це з'єднання видалено. item status description - - Most likely this contact has deleted the connection with you. - Швидше за все, цей контакт видалив зв'язок з вами. - No comment provided by engineer. - Multiple chat profiles Кілька профілів чату @@ -3802,10 +3887,17 @@ This is your link for group %@! Network connection + Підключення до мережі No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + Проблеми з мережею - термін дії повідомлення закінчився після багатьох спроб надіслати його. + snd error text + Network management + Керування мережею No comment provided by engineer. @@ -3920,6 +4012,7 @@ This is your link for group %@! No network connection + Немає підключення до мережі No comment provided by engineer. @@ -4138,6 +4231,7 @@ This is your link for group %@! Other + Інше No comment provided by engineer. @@ -4342,11 +4436,26 @@ Error: %@ Приватні імена файлів No comment provided by engineer. + + Private message routing + Маршрутизація приватних повідомлень + No comment provided by engineer. + + + Private message routing 🚀 + Маршрутизація приватних повідомлень 🚀 + No comment provided by engineer. + Private notes Приватні нотатки name of notes to self + + Private routing + Приватна маршрутизація + No comment provided by engineer. + Profile and server connections З'єднання профілю та сервера @@ -4359,6 +4468,7 @@ Error: %@ Profile images + Зображення профілю No comment provided by engineer. @@ -4403,6 +4513,7 @@ Error: %@ Prohibit sending SimpleX links. + Заборонити надсилання посилань SimpleX. No comment provided by engineer. @@ -4425,11 +4536,23 @@ Error: %@ Заборонити надсилання голосових повідомлень. No comment provided by engineer. + + Protect IP address + Захист IP-адреси + No comment provided by engineer. + Protect app screen Захистіть екран програми No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + Захистіть свою IP-адресу від ретрансляторів повідомлень, обраних вашими контактами. +Увімкніть у налаштуваннях *Мережа та сервери*. + No comment provided by engineer. + Protect your chat profiles with a password! Захистіть свої профілі чату паролем! @@ -4535,10 +4658,6 @@ Error: %@ Адреса отримувача буде змінена на інший сервер. Зміна адреси завершиться після того, як відправник з'явиться в мережі. No comment provided by engineer. - - Receiving concurrency - No comment provided by engineer. - Receiving file will be stopped. Отримання файлу буде зупинено. @@ -4556,6 +4675,7 @@ Error: %@ Recipient(s) can't see who this message is from. + Одержувач(и) не бачить, від кого це повідомлення. No comment provided by engineer. @@ -4773,6 +4893,11 @@ Error: %@ Сервери SMP No comment provided by engineer. + + Safely receive files + Безпечне отримання файлів + No comment provided by engineer. + Safer groups Безпечніші групи @@ -4860,6 +4985,7 @@ Error: %@ Saved + Збережено No comment provided by engineer. @@ -4869,6 +4995,7 @@ Error: %@ Saved from + Збережено з No comment provided by engineer. @@ -4996,6 +5123,16 @@ Error: %@ Надіслати живе повідомлення No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + Надсилайте повідомлення напряму, якщо IP-адреса захищена, а ваш сервер або сервер призначення не підтримує приватну маршрутизацію. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + Надсилайте повідомлення напряму, якщо ваш сервер або сервер призначення не підтримує приватну маршрутизацію. + No comment provided by engineer. + Send notifications Надсилати сповіщення @@ -5101,6 +5238,11 @@ Error: %@ Надіслані повідомлення будуть видалені через встановлений час. No comment provided by engineer. + + Server address is incompatible with network settings. + Адреса сервера несумісна з налаштуваннями мережі. + srv error text. + Server requires authorization to create queues, check password Сервер вимагає авторизації для створення черг, перевірте пароль @@ -5116,6 +5258,11 @@ Error: %@ Тест сервера завершився невдало! No comment provided by engineer. + + Server version is incompatible with network settings. + Серверна версія несумісна з мережевими налаштуваннями. + srv error text + Servers Сервери @@ -5178,6 +5325,7 @@ Error: %@ Shape profile images + Сформуйте зображення профілю No comment provided by engineer. @@ -5235,11 +5383,21 @@ Error: %@ Показати останні повідомлення No comment provided by engineer. + + Show message status + Показати статус повідомлення + No comment provided by engineer. + Show preview Показати попередній перегляд No comment provided by engineer. + + Show → on messages sent via private routing. + Показувати → у повідомленнях, надісланих через приватну маршрутизацію. + No comment provided by engineer. + Show: Показати: @@ -5302,10 +5460,12 @@ Error: %@ SimpleX links are prohibited in this group. + У цій групі заборонені посилання на SimpleX. No comment provided by engineer. SimpleX links not allowed + Посилання SimpleX заборонені No comment provided by engineer. @@ -5345,6 +5505,7 @@ Error: %@ Square, circle, or anything in between. + Квадрат, коло або щось середнє між ними. No comment provided by engineer. @@ -5559,6 +5720,11 @@ It can happen because of some bug or when the connection is compromised.Додаток може сповіщати вас, коли ви отримуєте повідомлення або запити на контакт - будь ласка, відкрийте налаштування, щоб увімкнути цю функцію. No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + Програма попросить підтвердити завантаження з невідомих файлових серверів (крім .onion). + No comment provided by engineer. + The attempt to change database passphrase was not completed. Спроба змінити пароль до бази даних не була завершена. @@ -5744,6 +5910,11 @@ It can happen because of some bug or when the connection is compromised.Для захисту часового поясу у файлах зображень/голосу використовується UTC. No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + Щоб захистити вашу IP-адресу, приватна маршрутизація використовує ваші SMP-сервери для доставки повідомлень. + No comment provided by engineer. + To protect your information, turn on SimpleX Lock. You will be prompted to complete authentication before this feature is enabled. @@ -5836,11 +6007,6 @@ You will be prompted to complete authentication before this feature is enabled.< Розблокувати учасника? No comment provided by engineer. - - Unexpected error: %@ - Неочікувана помилка: %@ - item status description - Unexpected migration state Неочікуваний стан міграції @@ -5886,6 +6052,11 @@ You will be prompted to complete authentication before this feature is enabled.< Невідома помилка No comment provided by engineer. + + Unknown servers! + Невідомі сервери! + No comment provided by engineer. + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. Якщо ви не користуєтеся інтерфейсом виклику iOS, увімкніть режим "Не турбувати", щоб уникнути переривань. @@ -6033,6 +6204,16 @@ To connect, please ask your contact to create another connection link and check Використовувати лише локальні сповіщення? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + Використовуйте приватну маршрутизацію з невідомими серверами, якщо IP-адреса не захищена. + No comment provided by engineer. + + + Use private routing with unknown servers. + Використовуйте приватну маршрутизацію з невідомими серверами. + No comment provided by engineer. + Use server Використовувати сервер @@ -6150,6 +6331,7 @@ To connect, please ask your contact to create another connection link and check Voice messages not allowed + Голосові повідомлення заборонені No comment provided by engineer. @@ -6224,6 +6406,7 @@ To connect, please ask your contact to create another connection link and check When connecting audio and video calls. + При підключенні аудіо та відеодзвінків. No comment provided by engineer. @@ -6238,14 +6421,17 @@ To connect, please ask your contact to create another connection link and check WiFi + WiFi No comment provided by engineer. Will be enabled in direct chats! + Буде ввімкнено в прямих чатах! No comment provided by engineer. Wired ethernet + Дротова мережа Ethernet No comment provided by engineer. @@ -6263,11 +6449,26 @@ To connect, please ask your contact to create another connection link and check З меншим споживанням заряду акумулятора. No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + Без Tor або VPN ваша IP-адреса буде видимою для файлових серверів. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + Без Tor або VPN ваша IP-адреса буде видимою для цих XFTP-ретрансляторів: %@. + No comment provided by engineer. + Wrong database passphrase Неправильний пароль до бази даних No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + Неправильний ключ або невідоме з'єднання - швидше за все, це з'єднання видалено. + snd error text + Wrong passphrase! Неправильний пароль! @@ -6733,6 +6934,7 @@ SimpleX servers cannot see your profile. admins + адміністратори feature role @@ -6747,6 +6949,7 @@ SimpleX servers cannot see your profile. all members + всі учасники feature role @@ -7081,6 +7284,7 @@ SimpleX servers cannot see your profile. forwarded + переслано No comment provided by engineer. @@ -7292,6 +7496,7 @@ SimpleX servers cannot see your profile. owners + власники feature role @@ -7346,10 +7551,12 @@ SimpleX servers cannot see your profile. saved + збережено No comment provided by engineer. saved from %@ + збережено з %@ No comment provided by engineer. @@ -7377,6 +7584,12 @@ SimpleX servers cannot see your profile. надіслати пряме повідомлення No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address встановити нову контактну адресу @@ -7417,11 +7630,21 @@ SimpleX servers cannot see your profile. невідомий connection info + + unknown relays + невідомі реле + No comment provided by engineer. + unknown status невідомий статус No comment provided by engineer. + + unprotected + незахищені + No comment provided by engineer. + updated group profile оновлений профіль групи @@ -7487,6 +7710,11 @@ SimpleX servers cannot see your profile. тижнів time unit + + when IP hidden + коли IP приховано + No comment provided by engineer. + yes так @@ -7494,6 +7722,7 @@ SimpleX servers cannot see your profile. you + ти No comment provided by engineer. diff --git a/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff index 4bf9e05665..9808c6fbcd 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 @@ -623,6 +623,7 @@ Admins can block a member for all. + 管理员可以为所有人封禁一名成员。 No comment provided by engineer. @@ -681,6 +682,7 @@ All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays. + 你的所有联系人、对话和文件将被安全加密并分块上传到配置的 XFTP 中继。 No comment provided by engineer. @@ -698,6 +700,10 @@ 仅当您的联系人允许时才允许限时消息。 No comment provided by engineer. + + Allow downgrade + No comment provided by engineer. + Allow irreversible message deletion only if your contact allows it to you. (24 hours) 仅有您的联系人许可后才允许不可撤回消息移除。 @@ -730,6 +736,7 @@ Allow to send SimpleX links. + 允许发送 SimpleX 链接。 No comment provided by engineer. @@ -792,6 +799,10 @@ 已经加入了该群组! No comment provided by engineer. + + Always use private routing. + No comment provided by engineer. + Always use relay 一直使用中继 @@ -814,6 +825,7 @@ App data migration + 应用数据迁移 No comment provided by engineer. @@ -853,14 +865,17 @@ Apply + 应用 No comment provided by engineer. Archive and upload + 存档和上传 No comment provided by engineer. Archiving database + 正在存档数据库 No comment provided by engineer. @@ -1055,6 +1070,7 @@ Cancel migration + 取消迁移 No comment provided by engineer. @@ -1067,8 +1083,13 @@ 无法接收文件 No comment provided by engineer. + + Capacity exceeded - recipient did not receive previously sent messages. + snd error text + Cellular + 移动网络 No comment provided by engineer. @@ -1164,6 +1185,7 @@ Chat migrated! + 已迁移聊天! No comment provided by engineer. @@ -1260,8 +1282,13 @@ 确认数据库升级 No comment provided by engineer. + + Confirm files from unknown servers. + No comment provided by engineer. + Confirm network settings + 确认网络设置 No comment provided by engineer. @@ -1276,10 +1303,12 @@ Confirm that you remember database passphrase to migrate it. + 请在迁移前确认你记得数据库的密码短语。 No comment provided by engineer. Confirm upload + 确认上传 No comment provided by engineer. @@ -1535,6 +1564,7 @@ This is your own one-time link! Creating archive link + 正在创建存档链接 No comment provided by engineer. @@ -1665,6 +1695,10 @@ This is your own one-time link! 应用程序重新启动时将迁移数据库 No comment provided by engineer. + + Debug delivery + No comment provided by engineer. + Decentralized 分散式 @@ -1756,6 +1790,7 @@ This cannot be undone! Delete database from this device + 从这部设备上删除数据库 No comment provided by engineer. @@ -1907,6 +1942,10 @@ This cannot be undone! 桌面设备 No comment provided by engineer. + + Destination server error: %@ + snd error text + Develop 开发 @@ -2012,11 +2051,19 @@ This cannot be undone! 通过本地网络发现 No comment provided by engineer. + + Do NOT send messages directly, even if your or destination server does not support private routing. + No comment provided by engineer. + Do NOT use SimpleX for emergency calls. 请勿使用 SimpleX 进行紧急通话。 No comment provided by engineer. + + Do NOT use private routing. + No comment provided by engineer. + Do it later 稍后再做 @@ -2049,10 +2096,12 @@ This cannot be undone! Download + 下载 chat item action Download failed + 下载失败了 No comment provided by engineer. @@ -2062,10 +2111,12 @@ This cannot be undone! Downloading archive + 正在下载存档 No comment provided by engineer. Downloading link details + 正在下载链接详情 No comment provided by engineer. @@ -2125,6 +2176,7 @@ This cannot be undone! Enable in direct chats (BETA)! + 在私聊中开启(公测)! No comment provided by engineer. @@ -2159,6 +2211,7 @@ This cannot be undone! Enabled for + 启用对象 No comment provided by engineer. @@ -2246,6 +2299,7 @@ This cannot be undone! Enter passphrase + 输入密码短语 No comment provided by engineer. @@ -2399,6 +2453,7 @@ This cannot be undone! Error downloading the archive + 下载存档出错 No comment provided by engineer. @@ -2477,6 +2532,7 @@ This cannot be undone! Error saving settings + 保存设置出错 when migrating @@ -2550,10 +2606,12 @@ This cannot be undone! Error uploading the archive + 上传存档出错 No comment provided by engineer. Error verifying passphrase: + 验证密码短语出错: No comment provided by engineer. @@ -2564,7 +2622,7 @@ This cannot be undone! Error: %@ 错误: %@ - No comment provided by engineer. + snd error text Error: URL is invalid @@ -2608,6 +2666,7 @@ This cannot be undone! Exported file doesn't exist + 导出的文件不存在 No comment provided by engineer. @@ -2655,6 +2714,10 @@ This cannot be undone! 文件:%@ No comment provided by engineer. + + Files + No comment provided by engineer. + Files & media 文件和媒体 @@ -2672,6 +2735,7 @@ This cannot be undone! Files and media not allowed + 不允许文件和媒体 No comment provided by engineer. @@ -2686,10 +2750,12 @@ This cannot be undone! Finalize migration + 完成迁移 No comment provided by engineer. Finalize migration on another device. + 在另一部设备上完成迁移 No comment provided by engineer. @@ -2739,20 +2805,34 @@ This cannot be undone! Forward + 转发 chat item action Forward and save messages + 转发并保存消息 No comment provided by engineer. Forwarded + 已转发 No comment provided by engineer. Forwarded from + 转发自 No comment provided by engineer. + + Forwarding server: %1$@ +Destination server error: %2$@ + snd error text + + + Forwarding server: %1$@ +Error: %2$@ + snd error text + Found desktop 找到了桌面 @@ -2864,6 +2944,7 @@ This cannot be undone! Group members can send SimpleX links. + 群成员可发送 SimpleX 链接。 No comment provided by engineer. @@ -3072,10 +3153,12 @@ This cannot be undone! Import failed + 导入失败了 No comment provided by engineer. Importing archive + 正在导入存档 No comment provided by engineer. @@ -3095,6 +3178,7 @@ This cannot be undone! In order to continue, chat should be stopped. + 必须停止聊天才能继续。 No comment provided by engineer. @@ -3104,6 +3188,7 @@ This cannot be undone! In-call sounds + 通话声音 No comment provided by engineer. @@ -3210,10 +3295,12 @@ This cannot be undone! Invalid link + 无效链接 No comment provided by engineer. Invalid migration confirmation + 迁移确认无效 No comment provided by engineer. @@ -3554,11 +3641,19 @@ This is your link for group %@! 消息送达回执! No comment provided by engineer. + + Message delivery warning + item status text + Message draft 消息草稿 No comment provided by engineer. + + Message queue info + No comment provided by engineer. + Message reactions 消息回应 @@ -3574,8 +3669,17 @@ This is your link for group %@! 该群组禁用了消息回应。 No comment provided by engineer. + + Message routing fallback + No comment provided by engineer. + + + Message routing mode + No comment provided by engineer. + Message source remains private. + 消息来源保持私密。 No comment provided by engineer. @@ -3585,6 +3689,7 @@ This is your link for group %@! Message too large + 消息太大了 No comment provided by engineer. @@ -3611,26 +3716,32 @@ This is your link for group %@! 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. @@ -3640,6 +3751,7 @@ This is your link for group %@! Migration complete + 迁移完毕 No comment provided by engineer. @@ -3684,6 +3796,7 @@ This is your link for group %@! More reliable network connection. + 更可靠的网络连接。 No comment provided by engineer. @@ -3691,11 +3804,6 @@ This is your link for group %@! 此连接很可能已被删除。 item status description - - Most likely this contact has deleted the connection with you. - 很可能此联系人已经删除了与您的联系。 - No comment provided by engineer. - Multiple chat profiles 多个聊天资料 @@ -3723,10 +3831,16 @@ This is your link for group %@! Network connection + 网络连接 No comment provided by engineer. + + Network issues - message expired after many attempts to send it. + snd error text + Network management + 网络管理 No comment provided by engineer. @@ -3841,6 +3955,7 @@ This is your link for group %@! No network connection + 无网络连接 No comment provided by engineer. @@ -4037,6 +4152,7 @@ This is your link for group %@! Or paste archive link + 或粘贴存档链接 No comment provided by engineer. @@ -4046,6 +4162,7 @@ This is your link for group %@! Or securely share this file link + 或安全地分享此文件链接 No comment provided by engineer. @@ -4055,6 +4172,7 @@ This is your link for group %@! Other + 其他 No comment provided by engineer. @@ -4138,6 +4256,7 @@ This is your link for group %@! Picture-in-picture calls + 画中画通话 No comment provided by engineer. @@ -4162,6 +4281,7 @@ This is your link for group %@! Please confirm that network settings are correct for this device. + 请确认网络设置对此这台设备正确无误。 No comment provided by engineer. @@ -4254,11 +4374,23 @@ Error: %@ 私密文件名 No comment provided by engineer. + + Private message routing + No comment provided by engineer. + + + Private message routing 🚀 + No comment provided by engineer. + Private notes 私密笔记 name of notes to self + + Private routing + No comment provided by engineer. + Profile and server connections 资料和服务器连接 @@ -4271,6 +4403,7 @@ Error: %@ Profile images + 个人资料图 No comment provided by engineer. @@ -4336,11 +4469,20 @@ Error: %@ 禁止发送语音消息。 No comment provided by engineer. + + Protect IP address + No comment provided by engineer. + Protect app screen 保护应用程序屏幕 No comment provided by engineer. + + Protect your IP address from the messaging relays chosen by your contacts. +Enable in *Network & servers* settings. + No comment provided by engineer. + Protect your chat profiles with a password! 使用密码保护您的聊天资料! @@ -4367,6 +4509,7 @@ Error: %@ Quantum resistant encryption + 抗量子加密 No comment provided by engineer. @@ -4444,10 +4587,6 @@ Error: %@ 接收地址将变更到不同的服务器。地址更改将在发件人上线后完成。 No comment provided by engineer. - - Receiving concurrency - No comment provided by engineer. - Receiving file will be stopped. 即将停止接收文件。 @@ -4464,6 +4603,7 @@ Error: %@ Recipient(s) can't see who this message is from. + 收件人看不到这条消息来自何人。 No comment provided by engineer. @@ -4563,10 +4703,12 @@ Error: %@ Repeat download + 重复下载 No comment provided by engineer. Repeat import + 重复导入 No comment provided by engineer. @@ -4576,6 +4718,7 @@ Error: %@ Repeat upload + 重复上传 No comment provided by engineer. @@ -4678,8 +4821,13 @@ Error: %@ SMP 服务器 No comment provided by engineer. + + Safely receive files + No comment provided by engineer. + Safer groups + 更安全的群组 No comment provided by engineer. @@ -4764,6 +4912,7 @@ Error: %@ Saved + 已保存 No comment provided by engineer. @@ -4773,6 +4922,7 @@ Error: %@ Saved from + 保存自 No comment provided by engineer. @@ -4900,6 +5050,14 @@ Error: %@ 发送实时消息 No comment provided by engineer. + + Send messages directly when IP address is protected and your or destination server does not support private routing. + No comment provided by engineer. + + + Send messages directly when your or destination server does not support private routing. + No comment provided by engineer. + Send notifications 发送通知 @@ -5005,6 +5163,10 @@ Error: %@ 已发送的消息将在设定的时间后被删除。 No comment provided by engineer. + + Server address is incompatible with network settings. + srv error text. + Server requires authorization to create queues, check password 服务器需要授权才能创建队列,检查密码 @@ -5020,6 +5182,10 @@ Error: %@ 服务器测试失败! No comment provided by engineer. + + Server version is incompatible with network settings. + srv error text + Servers 服务器 @@ -5057,6 +5223,7 @@ Error: %@ Set passphrase + 设置密码短语 No comment provided by engineer. @@ -5081,6 +5248,7 @@ Error: %@ Shape profile images + 改变个人资料图形状 No comment provided by engineer. @@ -5120,6 +5288,7 @@ Error: %@ Show QR code + 显示二维码 No comment provided by engineer. @@ -5137,11 +5306,19 @@ Error: %@ 显示最近的消息 No comment provided by engineer. + + Show message status + No comment provided by engineer. + Show preview 显示预览 No comment provided by engineer. + + Show → on messages sent via private routing. + No comment provided by engineer. + Show: 显示: @@ -5204,10 +5381,12 @@ Error: %@ SimpleX links are prohibited in this group. + 此群禁止 SimpleX 链接。 No comment provided by engineer. SimpleX links not allowed + 不允许SimpleX 链接 No comment provided by engineer. @@ -5247,6 +5426,7 @@ Error: %@ Square, circle, or anything in between. + 方形、圆形、或两者之间的任意形状 No comment provided by engineer. @@ -5276,6 +5456,7 @@ Error: %@ Stop chat + 停止聊天程序 No comment provided by engineer. @@ -5320,6 +5501,7 @@ Error: %@ Stopping chat + 正在停止聊天 No comment provided by engineer. @@ -5459,6 +5641,10 @@ It can happen because of some bug or when the connection is compromised.该应用可以在您收到消息或联系人请求时通知您——请打开设置以启用通知。 No comment provided by engineer. + + The app will ask to confirm downloads from unknown file servers (except .onion). + No comment provided by engineer. + The attempt to change database passphrase was not completed. 更改数据库密码的尝试未完成。 @@ -5571,10 +5757,12 @@ It can happen because of some bug or when the connection is compromised. 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 @@ -5642,6 +5830,10 @@ It can happen because of some bug or when the connection is compromised.为了保护时区,图像/语音文件使用 UTC。 No comment provided by engineer. + + To protect your IP address, private routing uses your SMP servers to deliver messages. + 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. @@ -5733,11 +5925,6 @@ You will be prompted to complete authentication before this feature is enabled.< 解封成员吗? No comment provided by engineer. - - Unexpected error: %@ - 意外错误: %@ - item status description - Unexpected migration state 未预料的迁移状态 @@ -5783,6 +5970,10 @@ You will be prompted to complete authentication before this feature is enabled.< 未知错误 No comment provided by engineer. + + Unknown servers! + No comment provided by engineer. + Unless you use iOS call interface, enable Do Not Disturb mode to avoid interruptions. 除非您使用 iOS 通话界面,否则请启用请勿打扰模式以避免打扰。 @@ -5872,6 +6063,7 @@ To connect, please ask your contact to create another connection link and check Upload failed + 上传失败了 No comment provided by engineer. @@ -5881,6 +6073,7 @@ To connect, please ask your contact to create another connection link and check Uploading archive + 正在上传存档 No comment provided by engineer. @@ -5927,6 +6120,14 @@ To connect, please ask your contact to create another connection link and check Use only local notifications? No comment provided by engineer. + + Use private routing with unknown servers when IP address is not protected. + No comment provided by engineer. + + + Use private routing with unknown servers. + No comment provided by engineer. + Use server 使用服务器 @@ -5934,6 +6135,7 @@ To connect, please ask your contact to create another connection link and check Use the app while in the call. + 通话时使用本应用 No comment provided by engineer. @@ -5973,10 +6175,12 @@ To connect, please ask your contact to create another connection link and check Verify database passphrase + 验证数据库密码短语 No comment provided by engineer. Verify passphrase + 验证密码短语 No comment provided by engineer. @@ -6041,6 +6245,7 @@ To connect, please ask your contact to create another connection link and check Voice messages not allowed + 不允许语音消息 No comment provided by engineer. @@ -6074,6 +6279,7 @@ To connect, please ask your contact to create another connection link and check Warning: starting chat on multiple devices is not supported and will cause message delivery failures + 警告:不支持在多部设备上启动聊天,这么做会导致消息传送失败。 No comment provided by engineer. @@ -6098,6 +6304,7 @@ To connect, please ask your contact to create another connection link and check Welcome message is too long + 欢迎消息太大了 No comment provided by engineer. @@ -6112,6 +6319,7 @@ To connect, please ask your contact to create another connection link and check When connecting audio and video calls. + 连接音频和视频通话时。 No comment provided by engineer. @@ -6126,14 +6334,17 @@ To connect, please ask your contact to create another connection link and check WiFi + WiFi No comment provided by engineer. Will be enabled in direct chats! + 将在私聊中启用! No comment provided by engineer. Wired ethernet + 有线以太网 No comment provided by engineer. @@ -6151,11 +6362,23 @@ To connect, please ask your contact to create another connection link and check 降低了电量使用。 No comment provided by engineer. + + Without Tor or VPN, your IP address will be visible to file servers. + No comment provided by engineer. + + + Without Tor or VPN, your IP address will be visible to these XFTP relays: %@. + No comment provided by engineer. + Wrong database passphrase 数据库密码错误 No comment provided by engineer. + + Wrong key or unknown connection - most likely this connection is deleted. + snd error text + Wrong passphrase! 密码错误! @@ -6258,6 +6481,7 @@ Repeat join request? You can give another try. + 你可以再试一次。 No comment provided by engineer. @@ -6609,6 +6833,7 @@ SimpleX 服务器无法看到您的资料。 admins + 管理员 feature role @@ -6623,6 +6848,7 @@ SimpleX 服务器无法看到您的资料。 all members + 所有成员 feature role @@ -6955,6 +7181,7 @@ SimpleX 服务器无法看到您的资料。 forwarded + 已转发 No comment provided by engineer. @@ -7165,6 +7392,7 @@ SimpleX 服务器无法看到您的资料。 owners + 所有者 feature role @@ -7174,6 +7402,7 @@ SimpleX 服务器无法看到您的资料。 quantum resistant e2e encryption + 抗量子端到端加密 chat item text @@ -7218,6 +7447,7 @@ SimpleX 服务器无法看到您的资料。 saved + 已保存 No comment provided by engineer. @@ -7249,6 +7479,12 @@ SimpleX 服务器无法看到您的资料。 发送私信 No comment provided by engineer. + + server queue info: %1$@ + +last received msg: %2$@ + queue info + set new contact address 设置新的联系地址 @@ -7261,6 +7497,7 @@ SimpleX 服务器无法看到您的资料。 standard end-to-end encryption + 标准端到端加密 chat item text @@ -7287,11 +7524,19 @@ SimpleX 服务器无法看到您的资料。 未知 connection info + + unknown relays + No comment provided by engineer. + unknown status 未知状态 No comment provided by engineer. + + unprotected + No comment provided by engineer. + updated group profile 已更新的群组资料 @@ -7356,6 +7601,10 @@ SimpleX 服务器无法看到您的资料。 time unit + + when IP hidden + No comment provided by engineer. + yes @@ -7363,6 +7612,7 @@ SimpleX 服务器无法看到您的资料。 you + No comment provided by engineer. diff --git a/apps/ios/SimpleX NSE/de.lproj/InfoPlist.strings b/apps/ios/SimpleX NSE/de.lproj/InfoPlist.strings index 9c675514f4..6cc768efe1 100644 --- a/apps/ios/SimpleX NSE/de.lproj/InfoPlist.strings +++ b/apps/ios/SimpleX NSE/de.lproj/InfoPlist.strings @@ -5,5 +5,5 @@ "CFBundleName" = "SimpleX NSE"; /* Copyright (human-readable) */ -"NSHumanReadableCopyright" = "Copyright © 2022 SimpleX Chat. All rights reserved."; +"NSHumanReadableCopyright" = "Copyright © 2024 SimpleX Chat. All rights reserved."; diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj index 368c786651..e3c8d84eb4 100644 --- a/apps/ios/SimpleX.xcodeproj/project.pbxproj +++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj @@ -24,6 +24,11 @@ 5C029EAA283942EA004A9677 /* CallController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C029EA9283942EA004A9677 /* CallController.swift */; }; 5C05DF532840AA1D00C683F9 /* CallSettings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C05DF522840AA1D00C683F9 /* CallSettings.swift */; }; 5C063D2727A4564100AEC577 /* ChatPreviewView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */; }; + 5C0EA13B2C0B176B00AD2E5E /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1362C0B176B00AD2E5E /* libgmp.a */; }; + 5C0EA13C2C0B176B00AD2E5E /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1372C0B176B00AD2E5E /* libgmpxx.a */; }; + 5C0EA13D2C0B176B00AD2E5E /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1382C0B176B00AD2E5E /* libffi.a */; }; + 5C0EA13E2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA1392C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a */; }; + 5C0EA13F2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C0EA13A2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a */; }; 5C10D88828EED12E00E58BF0 /* ContactConnectionInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */; }; 5C10D88A28F187F300E58BF0 /* FullScreenMediaView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C10D88928F187F300E58BF0 /* FullScreenMediaView.swift */; }; 5C116CDC27AABE0400E66D01 /* ContactRequestView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */; }; @@ -76,11 +81,6 @@ 5C9CC7AD28C55D7800BEF955 /* DatabaseEncryptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9CC7AC28C55D7800BEF955 /* DatabaseEncryptionView.swift */; }; 5C9D13A3282187BB00AB8B43 /* WebRTC.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9D13A2282187BB00AB8B43 /* WebRTC.swift */; }; 5C9D811A2AA8727A001D49FD /* CryptoFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9D81182AA7A4F1001D49FD /* CryptoFile.swift */; }; - 5C9F3DCC2BF7A6900003B86B /* libHSsimplex-chat-5.8.0.1-BrjXjAnJqNV7yWXU89n05g.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DC72BF7A6900003B86B /* libHSsimplex-chat-5.8.0.1-BrjXjAnJqNV7yWXU89n05g.a */; }; - 5C9F3DCD2BF7A6900003B86B /* libHSsimplex-chat-5.8.0.1-BrjXjAnJqNV7yWXU89n05g-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DC82BF7A6900003B86B /* libHSsimplex-chat-5.8.0.1-BrjXjAnJqNV7yWXU89n05g-ghc9.6.3.a */; }; - 5C9F3DCE2BF7A6900003B86B /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DC92BF7A6900003B86B /* libgmp.a */; }; - 5C9F3DCF2BF7A6900003B86B /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DCA2BF7A6900003B86B /* libgmpxx.a */; }; - 5C9F3DD02BF7A6900003B86B /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DCB2BF7A6900003B86B /* libffi.a */; }; 5C9FD96E27A5D6ED0075386C /* SendMessageView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */; }; 5CA059DC279559F40002BEB4 /* Tests_iOS.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059DB279559F40002BEB4 /* Tests_iOS.swift */; }; 5CA059DE279559F40002BEB4 /* Tests_iOSLaunchTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA059DD279559F40002BEB4 /* Tests_iOSLaunchTests.swift */; }; @@ -276,6 +276,11 @@ 5C029EA9283942EA004A9677 /* CallController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallController.swift; sourceTree = ""; }; 5C05DF522840AA1D00C683F9 /* CallSettings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CallSettings.swift; sourceTree = ""; }; 5C063D2627A4564100AEC577 /* ChatPreviewView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChatPreviewView.swift; sourceTree = ""; }; + 5C0EA1362C0B176B00AD2E5E /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; + 5C0EA1372C0B176B00AD2E5E /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; + 5C0EA1382C0B176B00AD2E5E /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; + 5C0EA1392C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a"; sourceTree = ""; }; + 5C0EA13A2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a"; sourceTree = ""; }; 5C10D88728EED12E00E58BF0 /* ContactConnectionInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactConnectionInfo.swift; sourceTree = ""; }; 5C10D88928F187F300E58BF0 /* FullScreenMediaView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FullScreenMediaView.swift; sourceTree = ""; }; 5C116CDB27AABE0400E66D01 /* ContactRequestView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactRequestView.swift; sourceTree = ""; }; @@ -357,11 +362,6 @@ 5C9CC7AC28C55D7800BEF955 /* DatabaseEncryptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DatabaseEncryptionView.swift; sourceTree = ""; }; 5C9D13A2282187BB00AB8B43 /* WebRTC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRTC.swift; sourceTree = ""; }; 5C9D81182AA7A4F1001D49FD /* CryptoFile.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CryptoFile.swift; sourceTree = ""; }; - 5C9F3DC72BF7A6900003B86B /* libHSsimplex-chat-5.8.0.1-BrjXjAnJqNV7yWXU89n05g.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.1-BrjXjAnJqNV7yWXU89n05g.a"; sourceTree = ""; }; - 5C9F3DC82BF7A6900003B86B /* libHSsimplex-chat-5.8.0.1-BrjXjAnJqNV7yWXU89n05g-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.1-BrjXjAnJqNV7yWXU89n05g-ghc9.6.3.a"; sourceTree = ""; }; - 5C9F3DC92BF7A6900003B86B /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; }; - 5C9F3DCA2BF7A6900003B86B /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; }; - 5C9F3DCB2BF7A6900003B86B /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; }; 5C9FD96A27A56D4D0075386C /* JSON.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = JSON.swift; sourceTree = ""; }; 5C9FD96D27A5D6ED0075386C /* SendMessageView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SendMessageView.swift; sourceTree = ""; }; 5CA059C3279559F40002BEB4 /* SimpleXApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SimpleXApp.swift; sourceTree = ""; }; @@ -535,13 +535,13 @@ isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; files = ( - 5C9F3DCF2BF7A6900003B86B /* libgmpxx.a in Frameworks */, + 5C0EA13C2C0B176B00AD2E5E /* libgmpxx.a in Frameworks */, 5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */, - 5C9F3DCE2BF7A6900003B86B /* libgmp.a in Frameworks */, - 5C9F3DCC2BF7A6900003B86B /* libHSsimplex-chat-5.8.0.1-BrjXjAnJqNV7yWXU89n05g.a in Frameworks */, - 5C9F3DD02BF7A6900003B86B /* libffi.a in Frameworks */, + 5C0EA13B2C0B176B00AD2E5E /* libgmp.a in Frameworks */, + 5C0EA13D2C0B176B00AD2E5E /* libffi.a in Frameworks */, + 5C0EA13F2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a in Frameworks */, 5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */, - 5C9F3DCD2BF7A6900003B86B /* libHSsimplex-chat-5.8.0.1-BrjXjAnJqNV7yWXU89n05g-ghc9.6.3.a in Frameworks */, + 5C0EA13E2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a in Frameworks */, ); runOnlyForDeploymentPostprocessing = 0; }; @@ -609,11 +609,11 @@ 5C764E5C279C70B7000C6508 /* Libraries */ = { isa = PBXGroup; children = ( - 5C9F3DCB2BF7A6900003B86B /* libffi.a */, - 5C9F3DC92BF7A6900003B86B /* libgmp.a */, - 5C9F3DCA2BF7A6900003B86B /* libgmpxx.a */, - 5C9F3DC82BF7A6900003B86B /* libHSsimplex-chat-5.8.0.1-BrjXjAnJqNV7yWXU89n05g-ghc9.6.3.a */, - 5C9F3DC72BF7A6900003B86B /* libHSsimplex-chat-5.8.0.1-BrjXjAnJqNV7yWXU89n05g.a */, + 5C0EA1382C0B176B00AD2E5E /* libffi.a */, + 5C0EA1362C0B176B00AD2E5E /* libgmp.a */, + 5C0EA1372C0B176B00AD2E5E /* libgmpxx.a */, + 5C0EA1392C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc-ghc9.6.3.a */, + 5C0EA13A2C0B176B00AD2E5E /* libHSsimplex-chat-5.8.0.5-Idqi6HXqzzs2zrnyZtMyhc.a */, ); path = Libraries; sourceTree = ""; @@ -1580,7 +1580,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 217; + CURRENT_PROJECT_VERSION = 223; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1629,7 +1629,7 @@ CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 217; + CURRENT_PROJECT_VERSION = 223; DEAD_CODE_STRIPPING = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; @@ -1715,7 +1715,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 217; + CURRENT_PROJECT_VERSION = 223; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; GCC_OPTIMIZATION_LEVEL = s; @@ -1752,7 +1752,7 @@ CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements"; CODE_SIGN_IDENTITY = "Apple Development"; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 217; + CURRENT_PROJECT_VERSION = 223; DEVELOPMENT_TEAM = 5NN7GUYB6T; ENABLE_BITCODE = NO; ENABLE_CODE_COVERAGE = NO; @@ -1789,7 +1789,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 217; + CURRENT_PROJECT_VERSION = 223; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; @@ -1840,7 +1840,7 @@ CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES; CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES; CODE_SIGN_STYLE = Automatic; - CURRENT_PROJECT_VERSION = 217; + CURRENT_PROJECT_VERSION = 223; DEFINES_MODULE = YES; DEVELOPMENT_TEAM = 5NN7GUYB6T; DYLIB_COMPATIBILITY_VERSION = 1; diff --git a/apps/ios/SimpleXChat/APITypes.swift b/apps/ios/SimpleXChat/APITypes.swift index f27033824d..8a6cf13e58 100644 --- a/apps/ios/SimpleXChat/APITypes.swift +++ b/apps/ios/SimpleXChat/APITypes.swift @@ -82,6 +82,8 @@ public enum ChatCommand { case apiSetMemberSettings(groupId: Int64, groupMemberId: Int64, memberSettings: GroupMemberSettings) case apiContactInfo(contactId: Int64) case apiGroupMemberInfo(groupId: Int64, groupMemberId: Int64) + case apiContactQueueInfo(contactId: Int64) + case apiGroupMemberQueueInfo(groupId: Int64, groupMemberId: Int64) case apiSwitchContact(contactId: Int64) case apiSwitchGroupMember(groupId: Int64, groupMemberId: Int64) case apiAbortSwitchContact(contactId: Int64) @@ -228,6 +230,8 @@ public enum ChatCommand { case let .apiSetMemberSettings(groupId, groupMemberId, memberSettings): return "/_member settings #\(groupId) \(groupMemberId) \(encodeJSON(memberSettings))" case let .apiContactInfo(contactId): return "/_info @\(contactId)" case let .apiGroupMemberInfo(groupId, groupMemberId): return "/_info #\(groupId) \(groupMemberId)" + case let .apiContactQueueInfo(contactId): return "/_queue info @\(contactId)" + case let .apiGroupMemberQueueInfo(groupId, groupMemberId): return "/_queue info #\(groupId) \(groupMemberId)" case let .apiSwitchContact(contactId): return "/_switch @\(contactId)" case let .apiSwitchGroupMember(groupId, groupMemberId): return "/_switch #\(groupId) \(groupMemberId)" case let .apiAbortSwitchContact(contactId): return "/_abort switch @\(contactId)" @@ -371,6 +375,8 @@ public enum ChatCommand { case .apiSetMemberSettings: return "apiSetMemberSettings" case .apiContactInfo: return "apiContactInfo" case .apiGroupMemberInfo: return "apiGroupMemberInfo" + case .apiContactQueueInfo: return "apiContactQueueInfo" + case .apiGroupMemberQueueInfo: return "apiGroupMemberQueueInfo" case .apiSwitchContact: return "apiSwitchContact" case .apiSwitchGroupMember: return "apiSwitchGroupMember" case .apiAbortSwitchContact: return "apiAbortSwitchContact" @@ -512,6 +518,7 @@ public enum ChatResponse: Decodable, Error { case networkConfig(networkConfig: NetCfg) case contactInfo(user: UserRef, contact: Contact, connectionStats_: ConnectionStats?, customUserProfile: Profile?) case groupMemberInfo(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats_: ConnectionStats?) + case queueInfo(user: UserRef, rcvMsgInfo: RcvMsgInfo?, queueInfo: QueueInfo) case contactSwitchStarted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) case groupMemberSwitchStarted(user: UserRef, groupInfo: GroupInfo, member: GroupMember, connectionStats: ConnectionStats) case contactSwitchAborted(user: UserRef, contact: Contact, connectionStats: ConnectionStats) @@ -624,7 +631,7 @@ public enum ChatResponse: Decodable, Error { case sndFileCompleteXFTP(user: UserRef, chatItem: AChatItem, fileTransferMeta: FileTransferMeta) case sndStandaloneFileComplete(user: UserRef, fileTransferMeta: FileTransferMeta, rcvURIs: [String]) case sndFileCancelledXFTP(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta) - case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta) + case sndFileError(user: UserRef, chatItem_: AChatItem?, fileTransferMeta: FileTransferMeta, errorMessage: String) // call events case callInvitation(callInvitation: RcvCallInvitation) case callOffer(user: UserRef, contact: Contact, callType: CallType, offer: WebRTCSession, sharedKey: String?, askConfirmation: Bool) @@ -674,6 +681,7 @@ public enum ChatResponse: Decodable, Error { case .networkConfig: return "networkConfig" case .contactInfo: return "contactInfo" case .groupMemberInfo: return "groupMemberInfo" + case .queueInfo: return "queueInfo" case .contactSwitchStarted: return "contactSwitchStarted" case .groupMemberSwitchStarted: return "groupMemberSwitchStarted" case .contactSwitchAborted: return "contactSwitchAborted" @@ -832,6 +840,9 @@ public enum ChatResponse: Decodable, Error { case let .networkConfig(networkConfig): return String(describing: networkConfig) case let .contactInfo(u, contact, connectionStats_, customUserProfile): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats_: \(String(describing: connectionStats_))\ncustomUserProfile: \(String(describing: customUserProfile))") case let .groupMemberInfo(u, groupInfo, member, connectionStats_): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats_: \(String(describing: connectionStats_))") + case let .queueInfo(u, rcvMsgInfo, queueInfo): + let msgInfo = if let info = rcvMsgInfo { encodeJSON(rcvMsgInfo) } else { "none" } + return withUser(u, "rcvMsgInfo: \(msgInfo)\nqueueInfo: \(encodeJSON(queueInfo))") case let .contactSwitchStarted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") case let .groupMemberSwitchStarted(u, groupInfo, member, connectionStats): return withUser(u, "groupInfo: \(String(describing: groupInfo))\nmember: \(String(describing: member))\nconnectionStats: \(String(describing: connectionStats))") case let .contactSwitchAborted(u, contact, connectionStats): return withUser(u, "contact: \(String(describing: contact))\nconnectionStats: \(String(describing: connectionStats))") @@ -941,7 +952,7 @@ public enum ChatResponse: Decodable, Error { case let .sndFileCompleteXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) case let .sndStandaloneFileComplete(u, _, rcvURIs): return withUser(u, String(rcvURIs.count)) case let .sndFileCancelledXFTP(u, chatItem, _): return withUser(u, String(describing: chatItem)) - case let .sndFileError(u, chatItem, _): return withUser(u, String(describing: chatItem)) + case let .sndFileError(u, chatItem, _, err): return withUser(u, "error: \(String(describing: err))\nchatItem: \(String(describing: chatItem))") case let .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))") @@ -1753,7 +1764,6 @@ public enum ChatErrorType: Decodable { case groupMemberNotActive case groupMemberUserRemoved case groupMemberNotFound - case groupMemberIntroNotFound(contactName: ContactName) case groupCantResendInvitation(groupInfo: GroupInfo, contactName: ContactName) case groupInternal(message: String) case fileNotFound(message: String) @@ -2184,3 +2194,42 @@ public enum UserNetworkType: String, Codable { } } } + +public struct RcvMsgInfo: Codable { + var msgId: Int64 + var msgDeliveryId: Int64 + var msgDeliveryStatus: String + var agentMsgId: Int64 + var agentMsgMeta: String +} + +public struct QueueInfo: Codable { + var qiSnd: Bool + var qiNtf: Bool + var qiSub: QSub? + var qiSize: Int + var qiMsg: MsgInfo? +} + +public struct QSub: Codable { + var qSubThread: QSubThread + var qDelivered: String? +} + +public enum QSubThread: String, Codable { + case noSub + case subPending + case subThread + case prohibitSub +} + +public struct MsgInfo: Codable { + var msgId: String + var msgTs: Date + var msgType: MsgType +} + +public enum MsgType: String, Codable { + case message + case quota +} diff --git a/apps/ios/bg.lproj/Localizable.strings b/apps/ios/bg.lproj/Localizable.strings index 06d9f1f43e..5fe8fe909c 100644 --- a/apps/ios/bg.lproj/Localizable.strings +++ b/apps/ios/bg.lproj/Localizable.strings @@ -1779,7 +1779,7 @@ /* No comment provided by engineer. */ "Error: " = "Грешка: "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "Грешка: %@"; /* No comment provided by engineer. */ @@ -2568,9 +2568,6 @@ /* item status description */ "Most likely this connection is deleted." = "Най-вероятно тази връзка е изтрита."; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "Най-вероятно този контакт е изтрил връзката с вас."; - /* No comment provided by engineer. */ "Multiple chat profiles" = "Множество профили за чат"; @@ -3077,9 +3074,6 @@ /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Получаващият адрес ще бъде променен към друг сървър. Промяната на адреса ще завърши, след като подателят е онлайн."; -/* No comment provided by engineer. */ -"Receiving concurrency" = "Паралелност на получаване"; - /* No comment provided by engineer. */ "Receiving file will be stopped." = "Получаващият се файл ще бъде спрян."; @@ -3920,9 +3914,6 @@ /* rcv group event chat item */ "unblocked %@" = "отблокиран %@"; -/* item status description */ -"Unexpected error: %@" = "Неочаквана грешка: %@"; - /* No comment provided by engineer. */ "Unexpected migration state" = "Неочаквано състояние на миграция"; diff --git a/apps/ios/cs.lproj/Localizable.strings b/apps/ios/cs.lproj/Localizable.strings index e2087547bd..bbe46b225f 100644 --- a/apps/ios/cs.lproj/Localizable.strings +++ b/apps/ios/cs.lproj/Localizable.strings @@ -1461,7 +1461,7 @@ /* No comment provided by engineer. */ "Error: " = "Chyba: "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "Chyba: %@"; /* No comment provided by engineer. */ @@ -2082,9 +2082,6 @@ /* item status description */ "Most likely this connection is deleted." = "Pravděpodobně je toto spojení smazáno."; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "Tento kontakt s největší pravděpodobností smazal spojení s vámi."; - /* No comment provided by engineer. */ "Multiple chat profiles" = "Více chatovacích profilů"; @@ -3179,9 +3176,6 @@ /* No comment provided by engineer. */ "Unable to record voice message" = "Nelze nahrát hlasovou zprávu"; -/* item status description */ -"Unexpected error: %@" = "Neočekávaná chyba: %@"; - /* No comment provided by engineer. */ "Unexpected migration state" = "Neočekávaný stav přenášení"; diff --git a/apps/ios/de.lproj/Localizable.strings b/apps/ios/de.lproj/Localizable.strings index 5fc393fa1f..01a760721f 100644 --- a/apps/ios/de.lproj/Localizable.strings +++ b/apps/ios/de.lproj/Localizable.strings @@ -438,7 +438,7 @@ "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."; +"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-Relais hochgeladen."; /* No comment provided by engineer. */ "Allow" = "Erlauben"; @@ -449,6 +449,9 @@ /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Erlauben Sie verschwindende Nachrichten nur dann, wenn es Ihr Kontakt ebenfalls erlaubt."; +/* No comment provided by engineer. */ +"Allow downgrade" = "Herabstufung erlauben"; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Erlauben Sie das unwiederbringliche Löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt. (24 Stunden)"; @@ -509,6 +512,9 @@ /* pref value */ "always" = "Immer"; +/* No comment provided by engineer. */ +"Always use private routing." = "Sie nutzen immer privates Routing."; + /* No comment provided by engineer. */ "Always use relay" = "Über ein Relais verbinden"; @@ -716,6 +722,9 @@ /* No comment provided by engineer. */ "Cannot receive file" = "Datei kann nicht empfangen werden"; +/* snd error text */ +"Capacity exceeded - recipient did not receive previously sent messages." = "Kapazität überschritten - der Empfänger hat die zuvor gesendeten Nachrichten nicht empfangen."; + /* No comment provided by engineer. */ "Cellular" = "Zellulär"; @@ -852,6 +861,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Datenbank-Aktualisierungen bestätigen"; +/* No comment provided by engineer. */ +"Confirm files from unknown servers." = "Dateien von unbekannten Servern bestätigen."; + /* No comment provided by engineer. */ "Confirm network settings" = "Bestätigen Sie die Netzwerkeinstellungen"; @@ -1320,6 +1332,9 @@ /* No comment provided by engineer. */ "Desktop devices" = "Desktop-Geräte"; +/* snd error text */ +"Destination server error: %@" = "Zielserver-Fehler: %@"; + /* No comment provided by engineer. */ "Develop" = "Entwicklung"; @@ -1399,7 +1414,13 @@ "Do not send history to new members." = "Den Nachrichtenverlauf nicht an neue Mitglieder senden."; /* No comment provided by engineer. */ -"Do NOT use SimpleX for emergency calls." = "Nutzen Sie SimpleX nicht für Notrufe."; +"Do NOT send messages directly, even if your or destination server does not support private routing." = "Nachrichten werden nicht direkt versendet, selbst wenn Ihr oder der Zielserver kein privates Routing unterstützt."; + +/* No comment provided by engineer. */ +"Do NOT use private routing." = "Sie nutzen KEIN privates Routing."; + +/* No comment provided by engineer. */ +"Do NOT use SimpleX for emergency calls." = "SimpleX NICHT für Notrufe nutzen."; /* No comment provided by engineer. */ "Don't create address" = "Keine Adresse erstellt"; @@ -1779,7 +1800,7 @@ /* No comment provided by engineer. */ "Error: " = "Fehler: "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "Fehler: %@"; /* No comment provided by engineer. */ @@ -1839,6 +1860,9 @@ /* No comment provided by engineer. */ "File: %@" = "Datei: %@"; +/* No comment provided by engineer. */ +"Files" = "Dateien"; + /* No comment provided by engineer. */ "Files & media" = "Dateien & Medien"; @@ -1905,6 +1929,12 @@ /* No comment provided by engineer. */ "Forwarded from" = "Weitergeleitet aus"; +/* snd error text */ +"Forwarding server: %@\nDestination server error: %@" = "Weiterleitungsserver: %1$@\nZielserver Fehler: %2$@"; + +/* snd error text */ +"Forwarding server: %@\nError: %@" = "Weiterleitungsserver: %1$@\nFehler: %2$@"; + /* No comment provided by engineer. */ "Found desktop" = "Gefundener Desktop"; @@ -2460,6 +2490,9 @@ /* No comment provided by engineer. */ "Message delivery receipts!" = "Empfangsbestätigungen für Nachrichten!"; +/* item status text */ +"Message delivery warning" = "Warnung bei der Nachrichtenzustellung"; + /* No comment provided by engineer. */ "Message draft" = "Nachrichtenentwurf"; @@ -2475,6 +2508,12 @@ /* notification */ "message received" = "Nachricht empfangen"; +/* No comment provided by engineer. */ +"Message routing fallback" = "Fallback für das Nachrichten-Routing"; + +/* No comment provided by engineer. */ +"Message routing mode" = "Modus für das Nachrichten-Routing"; + /* No comment provided by engineer. */ "Message source remains private." = "Die Nachrichtenquelle bleibt privat."; @@ -2568,9 +2607,6 @@ /* item status description */ "Most likely this connection is deleted." = "Wahrscheinlich ist diese Verbindung gelöscht worden."; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "Dieser Kontakt hat sehr wahrscheinlich die Verbindung mit Ihnen gelöscht."; - /* No comment provided by engineer. */ "Multiple chat profiles" = "Mehrere Chat-Profile"; @@ -2589,6 +2625,9 @@ /* No comment provided by engineer. */ "Network connection" = "Netzwerkverbindung"; +/* snd error text */ +"Network issues - message expired after many attempts to send it." = "Netzwerk-Fehler - die Nachricht ist nach vielen Sende-Versuchen abgelaufen."; + /* No comment provided by engineer. */ "Network management" = "Netzwerk-Verwaltung"; @@ -2730,10 +2769,10 @@ "One-time invitation link" = "Einmal-Einladungslink"; /* No comment provided by engineer. */ -"Onion hosts will be required for connection. Requires enabling VPN." = "Für die Verbindung werden Onion-Hosts benötigt. Dies erfordert die Aktivierung eines VPNs."; +"Onion hosts will be required for connection. Requires enabling VPN." = "Für diese Verbindung werden Onion-Hosts benötigt. Dies erfordert die Aktivierung eines VPNs."; /* No comment provided by engineer. */ -"Onion hosts will be used when available. Requires enabling VPN." = "Onion-Hosts werden verwendet, sobald sie verfügbar sind. Dies erfordert die Aktivierung eines VPNs."; +"Onion hosts will be used when available. Requires enabling VPN." = "Wenn Onion-Hosts verfügbar sind, werden sie verwendet. Dies erfordert die Aktivierung eines VPNs."; /* No comment provided by engineer. */ "Onion hosts will not be used." = "Onion-Hosts werden nicht verwendet."; @@ -2951,9 +2990,18 @@ /* No comment provided by engineer. */ "Private filenames" = "Neutrale Dateinamen"; +/* No comment provided by engineer. */ +"Private message routing" = "Privates Nachrichten-Routing"; + +/* No comment provided by engineer. */ +"Private message routing 🚀" = "Privates Nachrichten-Routing 🚀"; + /* name of notes to self */ "Private notes" = "Private Notizen"; +/* No comment provided by engineer. */ +"Private routing" = "Privates Routing"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profil und Serververbindungen"; @@ -3005,9 +3053,15 @@ /* No comment provided by engineer. */ "Protect app screen" = "App-Bildschirm schützen"; +/* No comment provided by engineer. */ +"Protect IP address" = "IP-Adresse schützen"; + /* No comment provided by engineer. */ "Protect your chat profiles with a password!" = "Ihre Chat-Profile mit einem Passwort schützen!"; +/* No comment provided by engineer. */ +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Schützen Sie Ihre IP-Adresse vor den Nachrichten-Relais , die Ihre Kontakte ausgewählt haben.\nAktivieren Sie es in den *Netzwerk & Server* Einstellungen."; + /* No comment provided by engineer. */ "Protocol timeout" = "Protokollzeitüberschreitung"; @@ -3077,9 +3131,6 @@ /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Die Empfängeradresse wird auf einen anderen Server geändert. Der Adresswechsel wird abgeschlossen, wenn der Absender wieder online ist."; -/* No comment provided by engineer. */ -"Receiving concurrency" = "Gleichzeitiger Empfang"; - /* No comment provided by engineer. */ "Receiving file will be stopped." = "Der Empfang der Datei wird beendet."; @@ -3236,6 +3287,9 @@ /* No comment provided by engineer. */ "Run chat" = "Chat starten"; +/* No comment provided by engineer. */ +"Safely receive files" = "Dateien sicher empfangen"; + /* No comment provided by engineer. */ "Safer groups" = "Sicherere Gruppen"; @@ -3392,6 +3446,12 @@ /* No comment provided by engineer. */ "Send live message" = "Live Nachricht senden"; +/* No comment provided by engineer. */ +"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Nachrichten werden direkt versendet, wenn die IP-Adresse geschützt ist, und Ihr oder der Zielserver kein privates Routing unterstützt."; + +/* No comment provided by engineer. */ +"Send messages directly when your or destination server does not support private routing." = "Nachrichten werden direkt versendet, wenn Ihr oder der Zielserver kein privates Routing unterstützt."; + /* No comment provided by engineer. */ "Send notifications" = "Benachrichtigungen senden"; @@ -3455,6 +3515,9 @@ /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "Gesendete Nachrichten werden nach der eingestellten Zeit gelöscht."; +/* srv error text. */ +"Server address is incompatible with network settings." = "Die Server-Adresse ist nicht mit den Netzwerk-Einstellungen kompatibel."; + /* server test error */ "Server requires authorization to create queues, check password" = "Um Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort"; @@ -3464,6 +3527,9 @@ /* No comment provided by engineer. */ "Server test failed!" = "Server Test ist fehlgeschlagen!"; +/* srv error text */ +"Server version is incompatible with network settings." = "Die Server-Version ist nicht mit den Netzwerk-Einstellungen kompatibel."; + /* No comment provided by engineer. */ "Servers" = "Server"; @@ -3530,6 +3596,9 @@ /* No comment provided by engineer. */ "Share with contacts" = "Mit Kontakten teilen"; +/* No comment provided by engineer. */ +"Show → on messages sent via private routing." = "Bei Nachrichten, die über privates Routing versendet wurden, → anzeigen."; + /* No comment provided by engineer. */ "Show calls in phone history" = "Anrufliste anzeigen"; @@ -3539,6 +3608,9 @@ /* No comment provided by engineer. */ "Show last messages" = "Letzte Nachrichten anzeigen"; +/* No comment provided by engineer. */ +"Show message status" = "Nachrichtenstatus anzeigen"; + /* No comment provided by engineer. */ "Show preview" = "Vorschau anzeigen"; @@ -3746,6 +3818,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Wenn sie Nachrichten oder Kontaktanfragen empfangen, kann Sie die App benachrichtigen - Um dies zu aktivieren, öffnen Sie bitte die Einstellungen."; +/* No comment provided by engineer. */ +"The app will ask to confirm downloads from unknown file servers (except .onion)." = "Die App wird eine Bestätigung bei Downloads von unbekannten Datei-Servern anfordern (außer bei .onion)."; + /* No comment provided by engineer. */ "The attempt to change database passphrase was not completed." = "Die Änderung des Datenbank-Passworts konnte nicht abgeschlossen werden."; @@ -3866,6 +3941,9 @@ /* 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." = "Um Ihre Informationen zu schützen, schalten Sie die SimpleX-Sperre ein.\nSie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funktion aktiviert wird."; +/* No comment provided by engineer. */ +"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Zum Schutz Ihrer IP-Adresse, wird für die Nachrichten-Auslieferung privates Routing über Ihre konfigurierten SMP-Server genutzt."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Bitte erlauben Sie die Nutzung des Mikrofons, um Sprachnachrichten aufnehmen zu können."; @@ -3920,9 +3998,6 @@ /* rcv group event chat item */ "unblocked %@" = "%@ wurde freigegeben"; -/* item status description */ -"Unexpected error: %@" = "Unerwarteter Fehler: %@"; - /* No comment provided by engineer. */ "Unexpected migration state" = "Unerwarteter Migrationsstatus"; @@ -3953,6 +4028,12 @@ /* No comment provided by engineer. */ "Unknown error" = "Unbekannter Fehler"; +/* No comment provided by engineer. */ +"unknown relays" = "Unbekannte Relais"; + +/* No comment provided by engineer. */ +"Unknown servers!" = "Unbekannte Server!"; + /* No comment provided by engineer. */ "unknown status" = "unbekannter Gruppenmitglieds-Status"; @@ -3977,6 +4058,9 @@ /* No comment provided by engineer. */ "Unmute" = "Stummschaltung aufheben"; +/* No comment provided by engineer. */ +"unprotected" = "Ungeschützt"; + /* No comment provided by engineer. */ "Unread" = "Ungelesen"; @@ -4029,7 +4113,7 @@ "Use chat" = "Verwenden Sie Chat"; /* No comment provided by engineer. */ -"Use current profile" = "Das aktuelle Profil nutzen"; +"Use current profile" = "Aktuelles Profil nutzen"; /* No comment provided by engineer. */ "Use for new connections" = "Für neue Verbindungen nutzen"; @@ -4041,11 +4125,17 @@ "Use iOS call interface" = "iOS Anrufschnittstelle nutzen"; /* No comment provided by engineer. */ -"Use new incognito profile" = "Ein neues Inkognito-Profil nutzen"; +"Use new incognito profile" = "Neues Inkognito-Profil nutzen"; /* No comment provided by engineer. */ "Use only local notifications?" = "Nur lokale Benachrichtigungen nutzen?"; +/* No comment provided by engineer. */ +"Use private routing with unknown servers when IP address is not protected." = "Sie nutzen privates Routing mit unbekannten Servern, wenn Ihre IP-Adresse nicht geschützt ist."; + +/* No comment provided by engineer. */ +"Use private routing with unknown servers." = "Sie nutzen privates Routing mit unbekannten Servern."; + /* No comment provided by engineer. */ "Use server" = "Server nutzen"; @@ -4199,6 +4289,9 @@ /* No comment provided by engineer. */ "When connecting audio and video calls." = "Bei der Verbindung über Audio- und Video-Anrufe."; +/* No comment provided by engineer. */ +"when IP hidden" = "Wenn die IP-Adresse versteckt ist"; + /* No comment provided by engineer. */ "When people request to connect, you can accept or reject it." = "Wenn Personen eine Verbindung anfordern, können Sie diese annehmen oder ablehnen."; @@ -4223,9 +4316,18 @@ /* No comment provided by engineer. */ "With reduced battery usage." = "Mit reduziertem Akkuverbrauch."; +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to file servers." = "Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für Datei-Server sichtbar sein."; + +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für diese XFTP-Relais sichtbar sein: %@."; + /* No comment provided by engineer. */ "Wrong database passphrase" = "Falsches Datenbank-Passwort"; +/* snd error text */ +"Wrong key or unknown connection - most likely this connection is deleted." = "Falscher Schlüssel oder unbekannte Verbindung - höchstwahrscheinlich ist diese Verbindung gelöscht worden."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "Falsches Passwort!"; diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings index 8a61d6c438..01924b1903 100644 --- a/apps/ios/es.lproj/Localizable.strings +++ b/apps/ios/es.lproj/Localizable.strings @@ -449,6 +449,9 @@ /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Se permiten los mensajes temporales pero sólo si tu contacto también los permite para tí."; +/* No comment provided by engineer. */ +"Allow downgrade" = "Permitir versión anterior"; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Se permite la eliminación irreversible de mensajes pero sólo si tu contacto también la permite para tí. (24 horas)"; @@ -509,6 +512,9 @@ /* pref value */ "always" = "siempre"; +/* No comment provided by engineer. */ +"Always use private routing." = "Usar siempre enrutamiento privado."; + /* No comment provided by engineer. */ "Always use relay" = "Usar siempre retransmisor"; @@ -675,7 +681,7 @@ "Bulgarian, Finnish, Thai and Ukrainian - thanks to the users and [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!" = "Búlgaro, Finlandés, Tailandés y Ucraniano - gracias a los usuarios y [Weblate](https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat)!"; /* No comment provided by engineer. */ -"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Mediante perfil (por defecto) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; +"By chat profile (default) or [by connection](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)." = "Mediante perfil (predeterminado) o [por conexión](https://simplex.chat/blog/20230204-simplex-chat-v4-5-user-chat-profiles.html#transport-isolation) (BETA)."; /* No comment provided by engineer. */ "Call already ended!" = "¡La llamada ha terminado!"; @@ -716,6 +722,9 @@ /* No comment provided by engineer. */ "Cannot receive file" = "No se puede recibir el archivo"; +/* snd error text */ +"Capacity exceeded - recipient did not receive previously sent messages." = "Capacidad excedida - el destinatario no ha recibido los mensajes previos."; + /* No comment provided by engineer. */ "Cellular" = "Móvil"; @@ -852,6 +861,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Confirmar actualizaciones de la bases de datos"; +/* No comment provided by engineer. */ +"Confirm files from unknown servers." = "Confirma archivos de servidores desconocidos."; + /* No comment provided by engineer. */ "Confirm network settings" = "Confirmar configuración de red"; @@ -1162,13 +1174,13 @@ "Decryption error" = "Error descifrado"; /* pref value */ -"default (%@)" = "por defecto (%@)"; +"default (%@)" = "predeterminado (%@)"; /* No comment provided by engineer. */ -"default (no)" = "por defecto (no)"; +"default (no)" = "predeterminado (no)"; /* No comment provided by engineer. */ -"default (yes)" = "por defecto (sí)"; +"default (yes)" = "predeterminado (sí)"; /* chat item action */ "Delete" = "Eliminar"; @@ -1320,6 +1332,9 @@ /* No comment provided by engineer. */ "Desktop devices" = "Ordenadores"; +/* snd error text */ +"Destination server error: %@" = "Error del servidor de destino: %@"; + /* No comment provided by engineer. */ "Develop" = "Desarrollo"; @@ -1398,6 +1413,12 @@ /* 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 send messages directly, even if your or destination server does not support private routing." = "NO enviar mensajes directamente incluso si tu servidor o el de destino no soportan enrutamiento privado."; + +/* No comment provided by engineer. */ +"Do NOT use private routing." = "NO usar enrutamiento privado."; + /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "NO uses SimpleX para llamadas de emergencia."; @@ -1492,7 +1513,7 @@ "enabled" = "activado"; /* No comment provided by engineer. */ -"Enabled for" = "Activar para"; +"Enabled for" = "Activado para"; /* enabled status */ "enabled for contact" = "activado para el contacto"; @@ -1779,7 +1800,7 @@ /* No comment provided by engineer. */ "Error: " = "Error: "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "Error: %@"; /* No comment provided by engineer. */ @@ -1839,6 +1860,9 @@ /* No comment provided by engineer. */ "File: %@" = "Archivo: %@"; +/* No comment provided by engineer. */ +"Files" = "Archivos"; + /* No comment provided by engineer. */ "Files & media" = "Archivos y multimedia"; @@ -1905,6 +1929,12 @@ /* No comment provided by engineer. */ "Forwarded from" = "Reenviado por"; +/* snd error text */ +"Forwarding server: %@\nDestination server error: %@" = "Servidor de reenvío: %1$@\nError del servidor de destino: %2$@"; + +/* snd error text */ +"Forwarding server: %@\nError: %@" = "Servidor de reenvío: %1$@\nError: %2$@"; + /* No comment provided by engineer. */ "Found desktop" = "Ordenador encontrado"; @@ -2460,6 +2490,9 @@ /* No comment provided by engineer. */ "Message delivery receipts!" = "¡Confirmación de entrega de mensajes!"; +/* item status text */ +"Message delivery warning" = "Aviso de entrega de mensaje"; + /* No comment provided by engineer. */ "Message draft" = "Borrador de mensaje"; @@ -2475,6 +2508,12 @@ /* notification */ "message received" = "mensaje recibido"; +/* No comment provided by engineer. */ +"Message routing fallback" = "Enrutamiento de mensajes alternativo"; + +/* No comment provided by engineer. */ +"Message routing mode" = "Modo de enrutamiento de mensajes"; + /* No comment provided by engineer. */ "Message source remains private." = "El autor del mensaje se mantiene privado."; @@ -2568,9 +2607,6 @@ /* item status description */ "Most likely this connection is deleted." = "Probablemente la conexión ha sido eliminada."; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "Lo más probable es que este contacto haya eliminado la conexión contigo."; - /* No comment provided by engineer. */ "Multiple chat profiles" = "Múltiples perfiles"; @@ -2589,6 +2625,9 @@ /* No comment provided by engineer. */ "Network connection" = "Conexión de red"; +/* snd error text */ +"Network issues - message expired after many attempts to send it." = "Problema en la red - el mensaje ha expirado tras muchos intentos de envío."; + /* No comment provided by engineer. */ "Network management" = "Gestión de la red"; @@ -2951,9 +2990,18 @@ /* No comment provided by engineer. */ "Private filenames" = "Nombres de archivos privados"; +/* No comment provided by engineer. */ +"Private message routing" = "Enrutamiento privado de mensajes"; + +/* No comment provided by engineer. */ +"Private message routing 🚀" = "Enrutamiento privado de mensajes 🚀"; + /* name of notes to self */ "Private notes" = "Notas privadas"; +/* No comment provided by engineer. */ +"Private routing" = "Enrutamiento privado"; + /* No comment provided by engineer. */ "Profile and server connections" = "Datos del perfil y conexiones"; @@ -2961,7 +3009,7 @@ "Profile image" = "Imagen del perfil"; /* No comment provided by engineer. */ -"Profile images" = "Imágenes del perfil"; +"Profile images" = "Forma de los perfiles"; /* No comment provided by engineer. */ "Profile name" = "Nombre del perfil"; @@ -3005,9 +3053,15 @@ /* No comment provided by engineer. */ "Protect app screen" = "Proteger la pantalla de la aplicación"; +/* No comment provided by engineer. */ +"Protect IP address" = "Proteger dirección IP"; + /* No comment provided by engineer. */ "Protect your chat profiles with a password!" = "¡Protege tus perfiles con contraseña!"; +/* No comment provided by engineer. */ +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Protege tu dirección IP de los servidores de retransmisión elegidos por tus contactos.\nActívalo en ajustes de *Servidores y Redes*."; + /* No comment provided by engineer. */ "Protocol timeout" = "Tiempo de espera del protocolo"; @@ -3077,9 +3131,6 @@ /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "La dirección de recepción pasará a otro servidor. El cambio se completará cuando el remitente esté en línea."; -/* No comment provided by engineer. */ -"Receiving concurrency" = "Concurrencia en la recepción"; - /* No comment provided by engineer. */ "Receiving file will be stopped." = "Se detendrá la recepción del archivo."; @@ -3123,7 +3174,7 @@ "rejected call" = "llamada rechazada"; /* No comment provided by engineer. */ -"Relay server is only used if necessary. Another party can observe your IP address." = "El retransmisor sólo se usa en caso de necesidad. Un tercero podría ver tu IP."; +"Relay server is only used if necessary. Another party can observe your IP address." = "El servidor de retransmisión sólo se usa en caso de necesidad. Un tercero podría ver tu IP."; /* No comment provided by engineer. */ "Relay server protects your IP address, but it can observe the duration of the call." = "El servidor de retransmisión protege tu IP pero puede ver la duración de la llamada."; @@ -3192,7 +3243,7 @@ "Reset colors" = "Restablecer colores"; /* No comment provided by engineer. */ -"Reset to defaults" = "Restablecer valores por defecto"; +"Reset to defaults" = "Restablecer valores predetarminados"; /* No comment provided by engineer. */ "Restart the app to create a new chat profile" = "Reinicia la aplicación para crear un perfil nuevo"; @@ -3236,6 +3287,9 @@ /* No comment provided by engineer. */ "Run chat" = "Ejecutar chat"; +/* No comment provided by engineer. */ +"Safely receive files" = "Recibe archivos de forma segura"; + /* No comment provided by engineer. */ "Safer groups" = "Grupos más seguros"; @@ -3381,7 +3435,7 @@ "Send direct message" = "Enviar mensaje directo"; /* No comment provided by engineer. */ -"Send direct message to connect" = "Enviar mensaje directo para conectar"; +"Send direct message to connect" = "Envia un mensaje para conectar"; /* No comment provided by engineer. */ "Send disappearing message" = "Enviar mensaje temporal"; @@ -3392,6 +3446,12 @@ /* No comment provided by engineer. */ "Send live message" = "Mensaje en vivo"; +/* No comment provided by engineer. */ +"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Enviar mensajes directamente cuando tu dirección IP está protegida y tu servidor o el de destino no admitan enrutamiento privado."; + +/* No comment provided by engineer. */ +"Send messages directly when your or destination server does not support private routing." = "Enviar mensajes directamente cuando tu servidor o el de destino no admitan enrutamiento privado."; + /* No comment provided by engineer. */ "Send notifications" = "Enviar notificaciones"; @@ -3455,6 +3515,9 @@ /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "Los mensajes enviados se eliminarán una vez transcurrido el tiempo establecido."; +/* srv error text. */ +"Server address is incompatible with network settings." = "La dirección del servidor es incompatible con la configuración de la red."; + /* server test error */ "Server requires authorization to create queues, check password" = "El servidor requiere autorización para crear colas, comprueba la contraseña"; @@ -3464,6 +3527,9 @@ /* No comment provided by engineer. */ "Server test failed!" = "¡Error en prueba del servidor!"; +/* srv error text */ +"Server version is incompatible with network settings." = "La versión del servidor es incompatible con la configuración de red."; + /* No comment provided by engineer. */ "Servers" = "Servidores"; @@ -3530,6 +3596,9 @@ /* No comment provided by engineer. */ "Share with contacts" = "Compartir con contactos"; +/* No comment provided by engineer. */ +"Show → on messages sent via private routing." = "Mostrar → en mensajes con enrutamiento privado."; + /* No comment provided by engineer. */ "Show calls in phone history" = "Mostrar llamadas en el historial del teléfono"; @@ -3539,6 +3608,9 @@ /* No comment provided by engineer. */ "Show last messages" = "Mostrar último mensaje"; +/* No comment provided by engineer. */ +"Show message status" = "Estado del mensaje"; + /* No comment provided by engineer. */ "Show preview" = "Mostrar vista previa"; @@ -3746,6 +3818,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "La aplicación puede notificarte cuando recibas mensajes o solicitudes de contacto: por favor, abre la configuración para activarlo."; +/* No comment provided by engineer. */ +"The app will ask to confirm downloads from unknown file servers (except .onion)." = "La aplicación pedirá que confirmes las descargas desde servidores de archivos desconocidos (excepto .onion)."; + /* No comment provided by engineer. */ "The attempt to change database passphrase was not completed." = "El intento de cambiar la contraseña de la base de datos no se ha completado."; @@ -3866,6 +3941,9 @@ /* 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." = "Para proteger tu información, activa el Bloqueo SimpleX.\nSe te pedirá que completes la autenticación antes de activar esta función."; +/* No comment provided by engineer. */ +"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Para proteger tu dirección IP, el enrutamiento privado usa tus servidores SMP para enviar mensajes."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Para grabar el mensaje de voz concede permiso para usar el micrófono."; @@ -3920,9 +3998,6 @@ /* rcv group event chat item */ "unblocked %@" = "ha desbloqueado a %@"; -/* item status description */ -"Unexpected error: %@" = "Error inesperado: %@"; - /* No comment provided by engineer. */ "Unexpected migration state" = "Estado de migración inesperado"; @@ -3953,6 +4028,12 @@ /* No comment provided by engineer. */ "Unknown error" = "Error desconocido"; +/* No comment provided by engineer. */ +"unknown relays" = "servidor de retransmisión desconocido"; + +/* No comment provided by engineer. */ +"Unknown servers!" = "¡Servidores desconocidos!"; + /* No comment provided by engineer. */ "unknown status" = "estado desconocido"; @@ -3977,6 +4058,9 @@ /* No comment provided by engineer. */ "Unmute" = "Activar audio"; +/* No comment provided by engineer. */ +"unprotected" = "desprotegido"; + /* No comment provided by engineer. */ "Unread" = "No leído"; @@ -4046,6 +4130,12 @@ /* No comment provided by engineer. */ "Use only local notifications?" = "¿Usar sólo notificaciones locales?"; +/* No comment provided by engineer. */ +"Use private routing with unknown servers when IP address is not protected." = "Usar enrutamiento privado con servidores desconocidos cuando la dirección IP no está protegida."; + +/* No comment provided by engineer. */ +"Use private routing with unknown servers." = "Usar enrutamiento privado con servidores desconocidos."; + /* No comment provided by engineer. */ "Use server" = "Usar servidor"; @@ -4199,6 +4289,9 @@ /* No comment provided by engineer. */ "When connecting audio and video calls." = "Al iniciar llamadas de audio y vídeo."; +/* No comment provided by engineer. */ +"when IP hidden" = "con IP oculta"; + /* No comment provided by engineer. */ "When people request to connect, you can accept or reject it." = "Cuando alguien solicite conectarse podrás aceptar o rechazar la solicitud."; @@ -4223,9 +4316,18 @@ /* No comment provided by engineer. */ "With reduced battery usage." = "Con uso reducido de batería."; +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to file servers." = "Sin Tor o VPN, tu dirección IP será visible para los servidores de archivos."; + +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Sin Tor o VPN, tu dirección IP será visible para estos servidores XFTP: %@."; + /* No comment provided by engineer. */ "Wrong database passphrase" = "Contraseña de base de datos incorrecta"; +/* snd error text */ +"Wrong key or unknown connection - most likely this connection is deleted." = "Clave incorrecta o conexión desconocida - probablemente esta conexión fue eliminada."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "¡Contraseña incorrecta!"; diff --git a/apps/ios/fi.lproj/Localizable.strings b/apps/ios/fi.lproj/Localizable.strings index c71f9e089c..12d17aac36 100644 --- a/apps/ios/fi.lproj/Localizable.strings +++ b/apps/ios/fi.lproj/Localizable.strings @@ -1434,7 +1434,7 @@ /* No comment provided by engineer. */ "Error: " = "Virhe: "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "Virhe: %@"; /* No comment provided by engineer. */ @@ -2058,9 +2058,6 @@ /* item status description */ "Most likely this connection is deleted." = "Todennäköisesti tämä yhteys on poistettu."; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "Todennäköisesti tämä kontakti on poistanut yhteyden sinuun."; - /* No comment provided by engineer. */ "Multiple chat profiles" = "Useita keskusteluprofiileja"; @@ -3137,9 +3134,6 @@ /* No comment provided by engineer. */ "Unable to record voice message" = "Ääniviestiä ei voi tallentaa"; -/* item status description */ -"Unexpected error: %@" = "Odottamaton virhe: %@"; - /* No comment provided by engineer. */ "Unexpected migration state" = "Odottamaton siirtotila"; diff --git a/apps/ios/fr.lproj/Localizable.strings b/apps/ios/fr.lproj/Localizable.strings index 5e6c9c1b40..91b8d7c866 100644 --- a/apps/ios/fr.lproj/Localizable.strings +++ b/apps/ios/fr.lproj/Localizable.strings @@ -449,6 +449,9 @@ /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Autorise les messages éphémères seulement si votre contact vous l’autorise."; +/* No comment provided by engineer. */ +"Allow downgrade" = "Autoriser la rétrogradation"; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Autoriser la suppression irréversible des messages uniquement si votre contact vous l'autorise. (24 heures)"; @@ -509,6 +512,9 @@ /* pref value */ "always" = "toujours"; +/* No comment provided by engineer. */ +"Always use private routing." = "Toujours utiliser le routage privé."; + /* No comment provided by engineer. */ "Always use relay" = "Se connecter via relais"; @@ -716,6 +722,9 @@ /* No comment provided by engineer. */ "Cannot receive file" = "Impossible de recevoir le fichier"; +/* snd error text */ +"Capacity exceeded - recipient did not receive previously sent messages." = "Capacité dépassée - le destinataire n'a pas pu recevoir les messages envoyés précédemment."; + /* No comment provided by engineer. */ "Cellular" = "Cellulaire"; @@ -852,6 +861,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 files from unknown servers." = "Confirmer les fichiers provenant de serveurs inconnus."; + /* No comment provided by engineer. */ "Confirm network settings" = "Confirmer les paramètres réseau"; @@ -1306,7 +1318,7 @@ "Delivery receipts are disabled!" = "Les accusés de réception sont désactivés !"; /* No comment provided by engineer. */ -"Delivery receipts!" = "Justificatifs de réception!"; +"Delivery receipts!" = "Justificatifs de réception !"; /* No comment provided by engineer. */ "Description" = "Description"; @@ -1320,6 +1332,9 @@ /* No comment provided by engineer. */ "Desktop devices" = "Appareils de bureau"; +/* snd error text */ +"Destination server error: %@" = "Erreur du serveur de destination : %@"; + /* No comment provided by engineer. */ "Develop" = "Développer"; @@ -1398,6 +1413,12 @@ /* No comment provided by engineer. */ "Do not send history to new members." = "Ne pas envoyer d'historique aux nouveaux membres."; +/* No comment provided by engineer. */ +"Do NOT send messages directly, even if your or destination server does not support private routing." = "Ne pas envoyer de messages directement, même si votre serveur ou le serveur de destination ne prend pas en charge le routage privé."; + +/* No comment provided by engineer. */ +"Do NOT use private routing." = "Ne pas utiliser de routage privé."; + /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "N'utilisez PAS SimpleX pour les appels d'urgence."; @@ -1779,7 +1800,7 @@ /* No comment provided by engineer. */ "Error: " = "Erreur : "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "Erreur : %@"; /* No comment provided by engineer. */ @@ -1839,6 +1860,9 @@ /* No comment provided by engineer. */ "File: %@" = "Fichier : %@"; +/* No comment provided by engineer. */ +"Files" = "Fichiers"; + /* No comment provided by engineer. */ "Files & media" = "Fichiers & médias"; @@ -1876,7 +1900,7 @@ "Fix connection" = "Réparer la connexion"; /* No comment provided by engineer. */ -"Fix connection?" = "Réparer la connexion?"; +"Fix connection?" = "Réparer la connexion ?"; /* No comment provided by engineer. */ "Fix encryption after restoring backups." = "Réparer le chiffrement après la restauration des sauvegardes."; @@ -1905,6 +1929,12 @@ /* No comment provided by engineer. */ "Forwarded from" = "Transféré depuis"; +/* snd error text */ +"Forwarding server: %@\nDestination server error: %@" = "Serveur de transfert : %1$@\nErreur du serveur de destination : %2$@"; + +/* snd error text */ +"Forwarding server: %@\nError: %@" = "Serveur de transfert : %1$@\nErreur : %2$@"; + /* No comment provided by engineer. */ "Found desktop" = "Bureau trouvé"; @@ -2460,6 +2490,9 @@ /* No comment provided by engineer. */ "Message delivery receipts!" = "Accusés de réception des messages !"; +/* item status text */ +"Message delivery warning" = "Avertissement sur la distribution des messages"; + /* No comment provided by engineer. */ "Message draft" = "Brouillon de message"; @@ -2475,6 +2508,12 @@ /* notification */ "message received" = "message reçu"; +/* No comment provided by engineer. */ +"Message routing fallback" = "Rabattement du routage des messages"; + +/* No comment provided by engineer. */ +"Message routing mode" = "Mode de routage des messages"; + /* No comment provided by engineer. */ "Message source remains private." = "La source du message reste privée."; @@ -2568,9 +2607,6 @@ /* item status description */ "Most likely this connection is deleted." = "Connexion probablement supprimée."; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "Il est fort probable que ce contact ait supprimé la connexion avec vous."; - /* No comment provided by engineer. */ "Multiple chat profiles" = "Différents profils de chat"; @@ -2589,6 +2625,9 @@ /* No comment provided by engineer. */ "Network connection" = "Connexion au réseau"; +/* snd error text */ +"Network issues - message expired after many attempts to send it." = "Problèmes de réseau - le message a expiré après plusieurs tentatives d'envoi."; + /* No comment provided by engineer. */ "Network management" = "Gestion du réseau"; @@ -2951,9 +2990,18 @@ /* No comment provided by engineer. */ "Private filenames" = "Noms de fichiers privés"; +/* No comment provided by engineer. */ +"Private message routing" = "Routage privé des messages"; + +/* No comment provided by engineer. */ +"Private message routing 🚀" = "Routage privé des messages 🚀"; + /* name of notes to self */ "Private notes" = "Notes privées"; +/* No comment provided by engineer. */ +"Private routing" = "Routage privé"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profil et connexions au serveur"; @@ -3005,9 +3053,15 @@ /* No comment provided by engineer. */ "Protect app screen" = "Protéger l'écran de l'app"; +/* No comment provided by engineer. */ +"Protect IP address" = "Protéger l'adresse IP"; + /* No comment provided by engineer. */ "Protect your chat profiles with a password!" = "Protégez vos profils de chat par un mot de passe !"; +/* No comment provided by engineer. */ +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Protégez votre adresse IP des relais de messagerie choisis par vos contacts.\nActivez-le dans les paramètres *Réseau et serveurs*."; + /* No comment provided by engineer. */ "Protocol timeout" = "Délai du protocole"; @@ -3077,9 +3131,6 @@ /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "L'adresse de réception sera changée pour un autre serveur. Le changement d'adresse sera terminé lorsque l'expéditeur sera en ligne."; -/* No comment provided by engineer. */ -"Receiving concurrency" = "Réception simultanée"; - /* No comment provided by engineer. */ "Receiving file will be stopped." = "La réception du fichier sera interrompue."; @@ -3099,7 +3150,7 @@ "Reconnect all connected servers to force message delivery. It uses additional traffic." = "Reconnecter tous les serveurs connectés pour forcer la livraison des messages. Cette méthode utilise du trafic supplémentaire."; /* No comment provided by engineer. */ -"Reconnect servers?" = "Reconnecter les serveurs?"; +"Reconnect servers?" = "Reconnecter les serveurs ?"; /* No comment provided by engineer. */ "Record updated at" = "Enregistrement mis à jour le"; @@ -3162,7 +3213,7 @@ "Renegotiate encryption" = "Renégocier le chiffrement"; /* No comment provided by engineer. */ -"Renegotiate encryption?" = "Renégocier le chiffrement?"; +"Renegotiate encryption?" = "Renégocier le chiffrement ?"; /* No comment provided by engineer. */ "Repeat connection request?" = "Répéter la demande de connexion ?"; @@ -3236,6 +3287,9 @@ /* No comment provided by engineer. */ "Run chat" = "Exécuter le chat"; +/* No comment provided by engineer. */ +"Safely receive files" = "Réception de fichiers en toute sécurité"; + /* No comment provided by engineer. */ "Safer groups" = "Groupes plus sûrs"; @@ -3392,6 +3446,12 @@ /* No comment provided by engineer. */ "Send live message" = "Envoyer un message dynamique"; +/* No comment provided by engineer. */ +"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Envoyer les messages de manière directe lorsque l'adresse IP est protégée et que votre serveur ou le serveur de destination ne prend pas en charge le routage privé."; + +/* No comment provided by engineer. */ +"Send messages directly when your or destination server does not support private routing." = "Envoyez les messages de manière directe lorsque votre serveur ou le serveur de destination ne prend pas en charge le routage privé."; + /* No comment provided by engineer. */ "Send notifications" = "Envoi de notifications"; @@ -3455,6 +3515,9 @@ /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "Les messages envoyés seront supprimés après une durée déterminée."; +/* srv error text. */ +"Server address is incompatible with network settings." = "L'adresse du serveur est incompatible avec les paramètres du réseau."; + /* server test error */ "Server requires authorization to create queues, check password" = "Le serveur requiert une autorisation pour créer des files d'attente, vérifiez le mot de passe"; @@ -3464,6 +3527,9 @@ /* No comment provided by engineer. */ "Server test failed!" = "Échec du test du serveur !"; +/* srv error text */ +"Server version is incompatible with network settings." = "La version du serveur est incompatible avec les paramètres du réseau."; + /* No comment provided by engineer. */ "Servers" = "Serveurs"; @@ -3530,6 +3596,9 @@ /* No comment provided by engineer. */ "Share with contacts" = "Partager avec vos contacts"; +/* No comment provided by engineer. */ +"Show → on messages sent via private routing." = "Afficher → sur les messages envoyés via le routage privé."; + /* No comment provided by engineer. */ "Show calls in phone history" = "Afficher les appels dans l'historique du téléphone"; @@ -3539,6 +3608,9 @@ /* No comment provided by engineer. */ "Show last messages" = "Voir les derniers messages"; +/* No comment provided by engineer. */ +"Show message status" = "Afficher le statut du message"; + /* No comment provided by engineer. */ "Show preview" = "Afficher l'aperçu"; @@ -3746,6 +3818,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "L'application peut vous avertir lorsque vous recevez des messages ou des demandes de contact - veuillez ouvrir les paramètres pour les activer."; +/* No comment provided by engineer. */ +"The app will ask to confirm downloads from unknown file servers (except .onion)." = "L'application demandera de confirmer les téléchargements à partir de serveurs de fichiers inconnus (sauf .onion)."; + /* No comment provided by engineer. */ "The attempt to change database passphrase was not completed." = "La tentative de modification de la phrase secrète de la base de données n'a pas abouti."; @@ -3866,6 +3941,9 @@ /* 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." = "Pour protéger vos informations, activez la fonction SimpleX Lock.\nVous serez invité à confirmer l'authentification avant que cette fonction ne soit activée."; +/* No comment provided by engineer. */ +"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Pour protéger votre adresse IP, le routage privé utilise vos serveurs SMP pour délivrer les messages."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Pour enregistrer un message vocal, veuillez accorder la permission d'utiliser le microphone."; @@ -3920,9 +3998,6 @@ /* rcv group event chat item */ "unblocked %@" = "%@ débloqué"; -/* item status description */ -"Unexpected error: %@" = "Erreur inattendue : %@"; - /* No comment provided by engineer. */ "Unexpected migration state" = "État de la migration inattendu"; @@ -3953,6 +4028,12 @@ /* No comment provided by engineer. */ "Unknown error" = "Erreur inconnue"; +/* No comment provided by engineer. */ +"unknown relays" = "relais inconnus"; + +/* No comment provided by engineer. */ +"Unknown servers!" = "Serveurs inconnus !"; + /* No comment provided by engineer. */ "unknown status" = "statut inconnu"; @@ -3977,6 +4058,9 @@ /* No comment provided by engineer. */ "Unmute" = "Démute"; +/* No comment provided by engineer. */ +"unprotected" = "non protégé"; + /* No comment provided by engineer. */ "Unread" = "Non lu"; @@ -4046,6 +4130,12 @@ /* No comment provided by engineer. */ "Use only local notifications?" = "Utilisation de notifications locales uniquement ?"; +/* No comment provided by engineer. */ +"Use private routing with unknown servers when IP address is not protected." = "Utiliser le routage privé avec des serveurs inconnus lorsque l'adresse IP n'est pas protégée."; + +/* No comment provided by engineer. */ +"Use private routing with unknown servers." = "Utiliser le routage privé avec des serveurs inconnus."; + /* No comment provided by engineer. */ "Use server" = "Utiliser ce serveur"; @@ -4170,7 +4260,7 @@ "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"; +"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 !"; @@ -4199,6 +4289,9 @@ /* No comment provided by engineer. */ "When connecting audio and video calls." = "Lors des appels audio et vidéo."; +/* No comment provided by engineer. */ +"when IP hidden" = "lorsque l'IP est masquée"; + /* No comment provided by engineer. */ "When people request to connect, you can accept or reject it." = "Vous pouvez accepter ou refuser les demandes de contacts."; @@ -4223,9 +4316,18 @@ /* No comment provided by engineer. */ "With reduced battery usage." = "Consommation réduite de la batterie."; +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to file servers." = "Sans Tor ou un VPN, votre adresse IP sera visible par les serveurs de fichiers."; + +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Sans Tor ni VPN, votre adresse IP sera visible par ces relais XFTP : %@."; + /* No comment provided by engineer. */ "Wrong database passphrase" = "Mauvaise phrase secrète pour la base de données"; +/* snd error text */ +"Wrong key or unknown connection - most likely this connection is deleted." = "Clé erronée ou connexion non identifiée - il est très probable que cette connexion soit supprimée."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "Mauvaise phrase secrète !"; diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings index b590785606..9d04ebd4fb 100644 --- a/apps/ios/hu.lproj/Localizable.strings +++ b/apps/ios/hu.lproj/Localizable.strings @@ -83,7 +83,7 @@ "**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)."; +"**Most private**: do not use SimpleX Chat notifications server, check messages periodically in the background (depends on how often you use the app)." = "**Legprivátabb**: ne használja a SimpleX Chat értesítési kiszolgálót, rendszeresen ellenőrizze az üzeneteket a háttérben (attól függően, hogy milyen gyakran használja az alkalmazást)."; /* No comment provided by engineer. */ "**Please note**: using the same database on two devices will break the decryption of messages from your connections, as a security protection." = "**Megjegyzés**: ha két eszközön is ugyanazt az adatbázist használja, akkor biztonsági védelemként megszakítja a kapcsolataiból érkező üzenetek visszafejtését."; @@ -92,7 +92,7 @@ "**Please note**: you will NOT be able to recover or change passphrase if you lose it." = "**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."; +"**Recommended**: device token and notifications are sent to SimpleX Chat notification server, but not the message content, size or who it is from." = "**Javasolt**: az eszköztoken és az értesítések elküldésre kerülnek a SimpleX Chat értesítési kiszolgálóra, kivéve az üzenet tartalma, mérete vagy az, hogy kitől származik."; /* 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."; @@ -345,7 +345,7 @@ "Accept" = "Elfogadás"; /* No comment provided by engineer. */ -"Accept connection request?" = "Kapcsolatfelvétel elfogadása?"; +"Accept connection request?" = "Kapcsolódási kérelem elfogadása?"; /* notification body */ "Accept contact request from %@?" = "Elfogadja %@ kapcsolat kérését?"; @@ -444,16 +444,19 @@ "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."; +"Allow calls only if your contact allows them." = "A hívások kezdeményezése kizárólag abban az esetben van engedélyezve, ha az 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."; +"Allow disappearing messages only if your contact allows it to you." = "Az eltűnő üzenetek küldése kizárólag abban az esetben van engedélyezve, ha az 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)"; +"Allow downgrade" = "Korábbi verzióra történő visszatérés engedélyezése"; /* 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."; +"Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Az üzenetek végleges törlése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. (24 óra)"; + +/* No comment provided by engineer. */ +"Allow message reactions only if your contact allows them." = "Az üzenetreakciók küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi."; /* No comment provided by engineer. */ "Allow message reactions." = "Üzenetreakciók engedélyezése."; @@ -462,7 +465,7 @@ "Allow sending direct messages to members." = "Közvetlen üzenetek küldésének engedélyezése a tagok számára."; /* No comment provided by engineer. */ -"Allow sending disappearing messages." = "Eltűnő üzenetek küldésének engedélyezése."; +"Allow sending disappearing messages." = "Az eltűnő üzenetek küldése engedélyezve van."; /* No comment provided by engineer. */ "Allow to irreversibly delete sent messages. (24 hours)" = "Elküldött üzenetek végleges törlésének engedélyezése. (24 óra)"; @@ -477,25 +480,25 @@ "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."; +"Allow voice messages only if your contact allows them." = "A hangüzenetek küldése kizárólag abban az esetben van engedélyezve, ha az 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."; +"Allow your contacts adding message reactions." = "Az üzenetreakciók küldése engedélyezve van az ismerősei számára."; /* No comment provided by engineer. */ -"Allow your contacts to call you." = "Hívások engedélyezése ismerősök számára."; +"Allow your contacts to call you." = "A hívások kezdeményezése engedélyezve van az ismerősei számára."; /* No comment provided by engineer. */ -"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "Elküldött üzenetek végleges törlésének engedélyezése az ismerősök számára. (24 óra)"; +"Allow your contacts to irreversibly delete sent messages. (24 hours)" = "Az elküldött üzenetek végleges törlése engedélyezve van az ismerősei 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."; +"Allow your contacts to send disappearing messages." = "Az eltűnő üzenetek küldésének engedélyezése az ismerősei 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."; +"Allow your contacts to send voice messages." = "A hangüzenetek küldése engedélyezve van az ismerősei számára."; /* No comment provided by engineer. */ "Already connected?" = "Már kapcsolódott?"; @@ -509,6 +512,9 @@ /* pref value */ "always" = "mindig"; +/* No comment provided by engineer. */ +"Always use private routing." = "Mindig használjon privát útválasztást."; + /* No comment provided by engineer. */ "Always use relay" = "Mindig használjon átjátszó kiszolgálót"; @@ -573,10 +579,10 @@ "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."; +"Audio/video calls are prohibited." = "A hívások kezdeményezése le van tiltva ebben a csevegésben."; /* PIN entry */ -"Authentication cancelled" = "Hitelesítés megszakítva"; +"Authentication cancelled" = "Hitelesítés visszavonva"; /* No comment provided by engineer. */ "Authentication failed" = "Sikertelen hitelesítés"; @@ -594,7 +600,7 @@ "Auto-accept" = "Automatikus elfogadás"; /* No comment provided by engineer. */ -"Auto-accept contact requests" = "Ismerős jelölések automatikus elfogadása"; +"Auto-accept contact requests" = "Kapcsolódási kérelmek automatikus elfogadása"; /* No comment provided by engineer. */ "Auto-accept images" = "Fotók automatikus elfogadása"; @@ -636,7 +642,7 @@ "Block member" = "Tag blokkolása"; /* No comment provided by engineer. */ -"Block member for all?" = "Tag letiltása mindenki számára?"; +"Block member for all?" = "Mindenki számára letiltja ezt a tagot?"; /* No comment provided by engineer. */ "Block member?" = "Tag blokkolása?"; @@ -663,7 +669,7 @@ "Both you and your contact can irreversibly delete sent messages. (24 hours)" = "Mindkét fél törölheti véglegesen 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."; +"Both you and your contact can make calls." = "Mindkét fél tud hívásokat kezdeményezni."; /* No comment provided by engineer. */ "Both you and your contact can send disappearing messages." = "Mindkét fél küldhet eltűnő üzeneteket."; @@ -708,7 +714,7 @@ "Cancel migration" = "Átköltöztetés visszavonása"; /* feature offered item */ -"cancelled %@" = "%@ törölve"; +"cancelled %@" = "%@ visszavonva"; /* 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"; @@ -716,6 +722,9 @@ /* No comment provided by engineer. */ "Cannot receive file" = "Nem lehet fogadni a fájlt"; +/* snd error text */ +"Capacity exceeded - recipient did not receive previously sent messages." = "Kapacitás túllépés - a címzett nem kapta meg a korábban elküldött üzeneteket."; + /* No comment provided by engineer. */ "Cellular" = "Mobilhálózat"; @@ -805,7 +814,7 @@ "Chinese and Spanish interface" = "Kínai és spanyol kezelőfelület"; /* No comment provided by engineer. */ -"Choose _Migrate from another device_ on the new device and scan QR code." = "Válassza az _Átköltöztetés egy másik eszközről_ opciót az új eszközön és szkennelje be a QR-kódot."; +"Choose _Migrate from another device_ on the new device and scan QR code." = "Válassza az _Átköltöztetés egy másik eszközről_ opciót az új eszközön és olvassa be a QR-kódot."; /* No comment provided by engineer. */ "Choose file" = "Fájl kiválasztása"; @@ -817,13 +826,13 @@ "Clear" = "Kiürítés"; /* No comment provided by engineer. */ -"Clear conversation" = "Beszélgetés kiürítése"; +"Clear conversation" = "Üzenetek kiürítése"; /* No comment provided by engineer. */ -"Clear conversation?" = "Beszélgetés kiürítése?"; +"Clear conversation?" = "Üzenetek kiürítése?"; /* No comment provided by engineer. */ -"Clear private notes?" = "Privát jegyzetek törlése?"; +"Clear private notes?" = "Privát jegyzetek kiürítése?"; /* No comment provided by engineer. */ "Clear verification" = "Hitelesítés törlése"; @@ -852,6 +861,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Adatbázis frissítés megerősítése"; +/* No comment provided by engineer. */ +"Confirm files from unknown servers." = "Ismeretlen kiszolgálókról származó fájlok jóváhagyása."; + /* No comment provided by engineer. */ "Confirm network settings" = "Hálózati beállítások megerősítése"; @@ -1006,7 +1018,7 @@ "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."; +"Contacts can mark messages for deletion; you will be able to view them." = "Az ismerősei törlésre jelölhetnek üzeneteket; ön majd meg tudja nézni azokat."; /* No comment provided by engineer. */ "Continue" = "Folytatás"; @@ -1075,7 +1087,7 @@ "Creating link…" = "Hivatkozás létrehozása…"; /* No comment provided by engineer. */ -"creator" = "szerző"; +"creator" = "készítő"; /* No comment provided by engineer. */ "Current Passcode" = "Jelenlegi jelkód"; @@ -1183,7 +1195,7 @@ "Delete address?" = "Azonosító törlése?"; /* No comment provided by engineer. */ -"Delete after" = "Törlés miután"; +"Delete after" = "Törlés ennyi idő után"; /* No comment provided by engineer. */ "Delete all files" = "Minden fájl törlése"; @@ -1261,7 +1273,7 @@ "Delete messages" = "Üzenetek törlése"; /* No comment provided by engineer. */ -"Delete messages after" = "Üzenetek törlése miután"; +"Delete messages after" = "Üzenetek törlése ennyi idő után"; /* No comment provided by engineer. */ "Delete old database" = "Régi adatbázis törlése"; @@ -1320,6 +1332,9 @@ /* No comment provided by engineer. */ "Desktop devices" = "Számítógépek"; +/* snd error text */ +"Destination server error: %@" = "Célkiszolgáló hiba: %@"; + /* No comment provided by engineer. */ "Develop" = "Fejlesztés"; @@ -1348,7 +1363,7 @@ "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."; +"Direct messages between members are prohibited in this group." = "A közvetlen üzenetek küldése a tagok között le van tiltva ebben a csoportban."; /* No comment provided by engineer. */ "Disable (keep overrides)" = "Letiltás (felülírások megtartásával)"; @@ -1369,7 +1384,7 @@ "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."; +"Disappearing messages are prohibited in this chat." = "Az eltűnő üzenetek küldése le van tiltva ebben a csevegésben."; /* No comment provided by engineer. */ "Disappearing messages are prohibited in this group." = "Az eltűnő üzenetek küldése le van tiltva ebben a csoportban."; @@ -1398,6 +1413,12 @@ /* No comment provided by engineer. */ "Do not send history to new members." = "Az előzmények ne kerüljenek elküldésre az új tagok számára."; +/* No comment provided by engineer. */ +"Do NOT send messages directly, even if your or destination server does not support private routing." = "Ne küldjön üzeneteket közvetlenül, még akkor sem, ha az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást."; + +/* No comment provided by engineer. */ +"Do NOT use private routing." = "Ne használjon privát útválasztást."; + /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "NE használja a SimpleX-et segélyhívásokhoz."; @@ -1779,7 +1800,7 @@ /* No comment provided by engineer. */ "Error: " = "Hiba: "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "Hiba: %@"; /* No comment provided by engineer. */ @@ -1839,6 +1860,9 @@ /* No comment provided by engineer. */ "File: %@" = "Fájl: %@"; +/* No comment provided by engineer. */ +"Files" = "Fájlok"; + /* No comment provided by engineer. */ "Files & media" = "Fájlok és média"; @@ -1905,6 +1929,12 @@ /* No comment provided by engineer. */ "Forwarded from" = "Továbbítva innen:"; +/* snd error text */ +"Forwarding server: %@\nDestination server error: %@" = "Továbbító kiszolgáló: %1$@\nCélkiszolgáló hiba:%2$@"; + +/* snd error text */ +"Forwarding server: %@\nError: %@" = "Továbbító kiszolgáló: %1$@\nHiba: %2$@"; + /* No comment provided by engineer. */ "Found desktop" = "Megtalált számítógép"; @@ -2077,7 +2107,7 @@ "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 véglegesen 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:"; +"If you enter your self-destruct passcode while opening the app:" = "Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot:"; /* No comment provided by engineer. */ "If you need to use the chat now tap **Do it later** below (you will be offered to migrate the database when you restart the app)." = "Ha most kell használnia a csevegést, koppintson a ** Csináld később** elemre (az alkalmazás újraindításakor felajánlásra kerül az adatbázis áttelepítése)."; @@ -2245,16 +2275,16 @@ "Invite to group" = "Meghívás a csoportba"; /* No comment provided by engineer. */ -"invited" = "meghívta"; +"invited" = "meghíva"; /* rcv group event chat item */ -"invited %@" = "meghívta %@-t"; +"invited %@" = "meghívta őt: %@"; /* chat list item title */ "invited to connect" = "meghívta, hogy csatlakozzon"; /* rcv group event chat item */ -"invited via your group link" = "meghívta a csoport hivatkozásán keresztül"; +"invited via your group link" = "meghíva az ön 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."; @@ -2266,10 +2296,10 @@ "Irreversible message deletion" = "Végleges üzenettörlés"; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this chat." = "Ebben a csevegésben az üzenetek végleges törlése le van tiltva."; +"Irreversible message deletion is prohibited in this chat." = "Az üzenetek végleges törlése le van tiltva ebben a csevegésben."; /* No comment provided by engineer. */ -"Irreversible message deletion is prohibited in this group." = "Ebben a csoportban az üzenetek végleges törlése le van tiltva."; +"Irreversible message deletion is prohibited in this group." = "Az üzenetek végleges törlése le van tiltva ebben a csoportban."; /* No comment provided by engineer. */ "It allows having many anonymous connections without any shared data between them in a single chat profile." = "Lehetővé teszi, hogy egyetlen csevegőprofilon belül több anonim kapcsolat legyen, anélkül, hogy megosztott adatok lennének közöttük."; @@ -2344,7 +2374,7 @@ "Learn more" = "Tudjon meg többet"; /* No comment provided by engineer. */ -"Leave" = "Elhagy"; +"Leave" = "Elhagyás"; /* No comment provided by engineer. */ "Leave group" = "Csoport elhagyása"; @@ -2407,7 +2437,7 @@ "Make profile private!" = "Tegye priváttá a profilját!"; /* No comment provided by engineer. */ -"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Győződjön meg arról, hogy a %@ szervercímek megfelelő formátumúak, sorszeparáltak és nem duplikáltak (%@)."; +"Make sure %@ server addresses are in correct format, line separated and are not duplicated (%@)." = "Győződjön meg arról, hogy a %@ kiszolgálócí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."; @@ -2419,10 +2449,10 @@ "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"; +"Mark read" = "Olvasottnak jelölés"; /* No comment provided by engineer. */ -"Mark verified" = "Ellenőrzöttként jelölve"; +"Mark verified" = "Hitelesítés"; /* No comment provided by engineer. */ "Markdown in messages" = "Markdown az üzenetekben"; @@ -2460,6 +2490,9 @@ /* No comment provided by engineer. */ "Message delivery receipts!" = "Üzenetkézbesítési bizonylatok!"; +/* item status text */ +"Message delivery warning" = "Üzenet kézbesítési figyelmeztetés"; + /* No comment provided by engineer. */ "Message draft" = "Üzenetvázlat"; @@ -2467,14 +2500,20 @@ "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."; +"Message reactions are prohibited in this chat." = "Az üzenetreakciók küldése le van tiltva ebben a csevegésben."; /* No comment provided by engineer. */ -"Message reactions are prohibited in this group." = "Ebben a csoportban az üzenetreakciók le vannak tiltva."; +"Message reactions are prohibited in this group." = "Az üzenetreakciók küldése le van tiltva ebben a csoportban."; /* notification */ "message received" = "üzenet érkezett"; +/* No comment provided by engineer. */ +"Message routing fallback" = "Üzenet útválasztási tartalék"; + +/* No comment provided by engineer. */ +"Message routing mode" = "Üzenet útválasztási mód"; + /* No comment provided by engineer. */ "Message source remains private." = "Az üzenet forrása titokban marad."; @@ -2494,10 +2533,10 @@ "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." = "Az üzeneteket, fájlokat és hívásokat **végpontok közötti titkosítással** é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."; +"Messages, files and calls are protected by **end-to-end encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzeneteket, fájlokat és hívásokat **végpontok közötti titkosítással**, sérülés utáni titkosság-védelemmel és -helyreállítással, továbbá visszautasítással védi."; /* No comment provided by engineer. */ -"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzeneteket, fájlokat és hívásokat **végpontok közötti kvantumrezisztens titkosítással** é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."; +"Messages, files and calls are protected by **quantum resistant e2e encryption** with perfect forward secrecy, repudiation and break-in recovery." = "Az üzeneteket, fájlokat és hívásokat **végpontok közötti kvantumrezisztens titkosítással**, sérülés utáni titkosság-védelemmel és -helyreállítással, továbbá visszautasítással védi."; /* No comment provided by engineer. */ "Migrate device" = "Eszköz átköltöztetése"; @@ -2568,9 +2607,6 @@ /* 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"; @@ -2589,6 +2625,9 @@ /* No comment provided by engineer. */ "Network connection" = "Internetkapcsolat"; +/* snd error text */ +"Network issues - message expired after many attempts to send it." = "Hálózati problémák - az üzenet többszöri elküldési kísérlet után lejárt."; + /* No comment provided by engineer. */ "Network management" = "Hálózatkezelés"; @@ -2775,7 +2814,7 @@ "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."; +"Only your contact can send disappearing messages." = "Csak az ismerőse 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."; @@ -2951,9 +2990,18 @@ /* No comment provided by engineer. */ "Private filenames" = "Privát fájl nevek"; +/* No comment provided by engineer. */ +"Private message routing" = "Privát üzenet útválasztás"; + +/* No comment provided by engineer. */ +"Private message routing 🚀" = "Privát üzenet útválasztás 🚀"; + /* name of notes to self */ "Private notes" = "Privát jegyzetek"; +/* No comment provided by engineer. */ +"Private routing" = "Privát útválasztás"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profil és kiszolgálókapcsolatok"; @@ -2976,22 +3024,22 @@ "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."; +"Prohibit audio/video calls." = "A hívások kezdeményezése le van tiltva."; /* 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."; +"Prohibit message reactions." = "Az üzenetreakciók küldése le van tiltva."; /* 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 a tagok számára."; +"Prohibit sending direct messages to members." = "A közvetlen üzenetek küldése le van tiltva a tagok között."; /* No comment provided by engineer. */ -"Prohibit sending disappearing messages." = "Eltűnő üzenetek küldésének letiltása."; +"Prohibit sending disappearing messages." = "Az eltűnő üzenetek küldése le van tiltva."; /* No comment provided by engineer. */ "Prohibit sending files and media." = "Fájlok- és a médiatartalom küldés letiltása."; @@ -3000,14 +3048,20 @@ "Prohibit sending SimpleX links." = "A SimpleX hivatkozások küldése le van tiltva."; /* No comment provided by engineer. */ -"Prohibit sending voice messages." = "Hangüzenetek küldésének letiltása."; +"Prohibit sending voice messages." = "A hangüzenetek küldése le van tiltva."; /* No comment provided by engineer. */ "Protect app screen" = "Alkalmazás képernyőjének védelme"; +/* No comment provided by engineer. */ +"Protect IP address" = "Az IP-cím 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. */ +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Védje IP-címét az ismerősei által kiválasztott üzenetküldő átjátszókkal szemben.\nEngedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban."; + /* No comment provided by engineer. */ "Protocol timeout" = "Protokoll időtúllépés"; @@ -3077,9 +3131,6 @@ /* 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 concurrency" = "Egyidejű fogadás"; - /* No comment provided by engineer. */ "Receiving file will be stopped." = "A fájl fogadása leállt."; @@ -3132,10 +3183,10 @@ "Remove" = "Eltávolítás"; /* No comment provided by engineer. */ -"Remove member" = "Tag eltávolítása"; +"Remove member" = "Eltávolítás"; /* No comment provided by engineer. */ -"Remove member?" = "Tag eltávolítása?"; +"Remove member?" = "Biztosan eltávolítja?"; /* No comment provided by engineer. */ "Remove passphrase from keychain?" = "Jelmondat eltávolítása a kulcstárolóból?"; @@ -3236,6 +3287,9 @@ /* No comment provided by engineer. */ "Run chat" = "Csevegési szolgáltatás indítása"; +/* No comment provided by engineer. */ +"Safely receive files" = "Fájlok biztonságos fogadása"; + /* No comment provided by engineer. */ "Safer groups" = "Biztonságosabb csoportok"; @@ -3306,7 +3360,7 @@ "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"; +"Scan code" = "Beolvasás"; /* No comment provided by engineer. */ "Scan QR code" = "QR-kód beolvasása"; @@ -3392,6 +3446,12 @@ /* No comment provided by engineer. */ "Send live message" = "Élő üzenet küldése"; +/* No comment provided by engineer. */ +"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Közvetlen üzenetküldés, ha az IP-cím védett és az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást."; + +/* No comment provided by engineer. */ +"Send messages directly when your or destination server does not support private routing." = "Közvetlen üzenetküldés, ha az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást."; + /* No comment provided by engineer. */ "Send notifications" = "Értesítések küldése"; @@ -3411,7 +3471,7 @@ "Send up to 100 last messages to new members." = "Az utolsó 100 üzenet elküldése az új tagoknak."; /* No comment provided by engineer. */ -"Sender cancelled file transfer." = "A küldő megszakította a fájl átvitelt."; +"Sender cancelled file transfer." = "A fájl küldője visszavonta az átvitelt."; /* No comment provided by engineer. */ "Sender may have deleted the connection request." = "A küldő törölhette a kapcsolódási kérelmet."; @@ -3455,6 +3515,9 @@ /* 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."; +/* srv error text. */ +"Server address is incompatible with network settings." = "A kiszolgáló címe nem kompatibilis a hálózati beállításokkal."; + /* 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"; @@ -3464,6 +3527,9 @@ /* No comment provided by engineer. */ "Server test failed!" = "Sikertelen kiszolgáló-teszt!"; +/* srv error text */ +"Server version is incompatible with network settings." = "A kiszolgáló verziója nem kompatibilis a hálózati beállításokkal."; + /* No comment provided by engineer. */ "Servers" = "Kiszolgálók"; @@ -3530,6 +3596,9 @@ /* No comment provided by engineer. */ "Share with contacts" = "Megosztás ismerősökkel"; +/* No comment provided by engineer. */ +"Show → on messages sent via private routing." = "Egy „→” jel megjelenítése a privát útválasztáson keresztül küldött üzeneteknél."; + /* No comment provided by engineer. */ "Show calls in phone history" = "Hívások megjelenítése a híváslistában"; @@ -3539,6 +3608,9 @@ /* No comment provided by engineer. */ "Show last messages" = "Utolsó üzenetek megjelenítése"; +/* No comment provided by engineer. */ +"Show message status" = "Üzenet állapot megjelenítése"; + /* No comment provided by engineer. */ "Show preview" = "Előnézet megjelenítése"; @@ -3570,7 +3642,7 @@ "SimpleX links" = "SimpleX hivatkozások"; /* No comment provided by engineer. */ -"SimpleX links are prohibited in this group." = "A SimpleX hivatkozások küldése ebben a csoportban le van tiltva."; +"SimpleX links are prohibited in this group." = "A SimpleX hivatkozások küldése le van tiltva ebben a csoportban."; /* No comment provided by engineer. */ "SimpleX links not allowed" = "A SimpleX hivatkozások küldése le van tiltva"; @@ -3746,6 +3818,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Az alkalmazás értesíteni fogja, amikor üzeneteket vagy kapcsolatfelvételi kéréseket kap – beállítások megnyitása az engedélyezéshez."; +/* No comment provided by engineer. */ +"The app will ask to confirm downloads from unknown file servers (except .onion)." = "Az alkalmazás kérni fogja az ismeretlen fájlkiszolgálókról (kivéve .onion) történő letöltések megerősítését."; + /* 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."; @@ -3753,7 +3828,7 @@ "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!"; +"The connection you accepted will be cancelled!" = "Az ön által elfogadott kapcsolat vissza lesz vonva!"; /* No comment provided by engineer. */ "The contact you shared this link with will NOT be able to connect!" = "Ismerőse, akivel megosztotta ezt a hivatkozást, NEM fog tudni kapcsolódni!"; @@ -3866,6 +3941,9 @@ /* 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 protect your IP address, private routing uses your SMP servers to deliver messages." = "Az IP-címe védelme érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Hangüzenet rögzítéséhez adjon engedélyt a mikrofon használathoz."; @@ -3876,7 +3954,7 @@ "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."; +"To verify end-to-end encryption with your contact compare (or scan) the code on your devices." = "A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) az ismerőse eszközén lévő kóddal."; /* No comment provided by engineer. */ "Toggle incognito when connecting." = "Inkognitó mód kapcsolódáskor."; @@ -3920,9 +3998,6 @@ /* 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 átköltöztetési állapot"; @@ -3953,6 +4028,12 @@ /* No comment provided by engineer. */ "Unknown error" = "Ismeretlen hiba"; +/* No comment provided by engineer. */ +"unknown relays" = "ismeretlen átjátszók"; + +/* No comment provided by engineer. */ +"Unknown servers!" = "Ismeretlen kiszolgálók!"; + /* No comment provided by engineer. */ "unknown status" = "ismeretlen státusz"; @@ -3977,6 +4058,9 @@ /* No comment provided by engineer. */ "Unmute" = "Némítás feloldása"; +/* No comment provided by engineer. */ +"unprotected" = "nem védett"; + /* No comment provided by engineer. */ "Unread" = "Olvasatlan"; @@ -3999,13 +4083,13 @@ "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"; +"updated group profile" = "frissítette a csoport profilját"; /* 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."; +"Updating settings will re-connect the client to all servers." = "A beállítások frissítése a kiszolgálókhoz való ú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 újrakapcsolódik az összes kiszolgálóhoz."; @@ -4046,6 +4130,12 @@ /* No comment provided by engineer. */ "Use only local notifications?" = "Csak helyi értesítések használata?"; +/* No comment provided by engineer. */ +"Use private routing with unknown servers when IP address is not protected." = "Privát útválasztás használata ismeretlen kiszolgálókkal, ha az IP-cím nem védett."; + +/* No comment provided by engineer. */ +"Use private routing with unknown servers." = "Használjon privát útválasztást ismeretlen kiszolgálókkal."; + /* No comment provided by engineer. */ "Use server" = "Kiszolgáló használata"; @@ -4137,7 +4227,7 @@ "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."; +"Voice messages are prohibited in this chat." = "A hangüzenetek küldése le van tiltva ebben a csevegésben."; /* No comment provided by engineer. */ "Voice messages are prohibited in this group." = "A hangüzenetek küldése le van tiltva ebben a csoportban."; @@ -4149,7 +4239,7 @@ "Voice messages prohibited!" = "A hangüzenetek le vannak tilva!"; /* No comment provided by engineer. */ -"waiting for answer…" = "várakozás válaszra…"; +"waiting for answer…" = "várakozás a válaszra…"; /* No comment provided by engineer. */ "waiting for confirmation…" = "várakozás a visszaigazolásra…"; @@ -4199,6 +4289,9 @@ /* No comment provided by engineer. */ "When connecting audio and video calls." = "Amikor egy bejövő hang- vagy videóhívás érkezik."; +/* No comment provided by engineer. */ +"when IP hidden" = "ha az IP-cím rejtett"; + /* No comment provided by engineer. */ "When people request to connect, you can accept or reject it." = "Amikor az emberek kapcsolódást kérelmeznek, ön elfogadhatja vagy elutasíthatja azokat."; @@ -4223,9 +4316,18 @@ /* No comment provided by engineer. */ "With reduced battery usage." = "Csökkentett akkumulátorhasználattal."; +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to file servers." = "Tor vagy VPN nélkül az IP-címe látható lesz a fájlkiszolgálók számára."; + +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Tor vagy VPN nélkül az IP-címe látható lesz ezen XFTP átjátszók számára: %@."; + /* No comment provided by engineer. */ "Wrong database passphrase" = "Téves adatbázis jelmondat"; +/* snd error text */ +"Wrong key or unknown connection - most likely this connection is deleted." = "Rossz kulcs vagy ismeretlen kapcsolat - valószínűleg ez a kapcsolat törlődött."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "Téves jelmondat!"; @@ -4356,7 +4458,7 @@ "you changed role of %@ to %@" = "%1$@ szerepkörét megváltoztatta 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."; +"You control through which server(s) **to receive** the messages, your contacts – the servers you use to message them." = "Ön szabályozhatja, hogy mely kiszogál(ók)ón keresztül **kapja** az üzeneteket, az ismerősöket - az üzenetküldéshez használt kiszolgálókon."; /* No comment provided by engineer. */ "You could not be verified; please try again." = "Nem lehetett ellenőrizni; próbálja meg újra."; @@ -4461,7 +4563,7 @@ "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)."; +"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ősének online kell lennie ahhoz, hogy a kapcsolat létrejöjjön.\nVisszavonhatja 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 (%@)."; diff --git a/apps/ios/it.lproj/Localizable.strings b/apps/ios/it.lproj/Localizable.strings index e27b2e04a8..7039ebc4d0 100644 --- a/apps/ios/it.lproj/Localizable.strings +++ b/apps/ios/it.lproj/Localizable.strings @@ -449,6 +449,9 @@ /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Consenti i messaggi a tempo solo se il contatto li consente a te."; +/* No comment provided by engineer. */ +"Allow downgrade" = "Consenti downgrade"; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Consenti l'eliminazione irreversibile dei messaggi solo se il contatto la consente a te. (24 ore)"; @@ -509,6 +512,9 @@ /* pref value */ "always" = "sempre"; +/* No comment provided by engineer. */ +"Always use private routing." = "Usa sempre l'instradamento privato."; + /* No comment provided by engineer. */ "Always use relay" = "Connetti via relay"; @@ -716,6 +722,9 @@ /* No comment provided by engineer. */ "Cannot receive file" = "Impossibile ricevere il file"; +/* snd error text */ +"Capacity exceeded - recipient did not receive previously sent messages." = "Quota superata - il destinatario non ha ricevuto i messaggi precedentemente inviati."; + /* No comment provided by engineer. */ "Cellular" = "Mobile"; @@ -852,6 +861,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Conferma aggiornamenti database"; +/* No comment provided by engineer. */ +"Confirm files from unknown servers." = "Conferma i file da server sconosciuti."; + /* No comment provided by engineer. */ "Confirm network settings" = "Conferma le impostazioni di rete"; @@ -1320,6 +1332,9 @@ /* No comment provided by engineer. */ "Desktop devices" = "Dispositivi desktop"; +/* snd error text */ +"Destination server error: %@" = "Errore del server di destinazione: %@"; + /* No comment provided by engineer. */ "Develop" = "Sviluppa"; @@ -1398,6 +1413,12 @@ /* No comment provided by engineer. */ "Do not send history to new members." = "Non inviare la cronologia ai nuovi membri."; +/* No comment provided by engineer. */ +"Do NOT send messages directly, even if your or destination server does not support private routing." = "NON inviare messaggi direttamente, anche se il tuo server o quello di destinazione non supporta l'instradamento privato."; + +/* No comment provided by engineer. */ +"Do NOT use private routing." = "NON usare l'instradamento privato."; + /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "NON usare SimpleX per chiamate di emergenza."; @@ -1779,7 +1800,7 @@ /* No comment provided by engineer. */ "Error: " = "Errore: "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "Errore: %@"; /* No comment provided by engineer. */ @@ -1839,6 +1860,9 @@ /* No comment provided by engineer. */ "File: %@" = "File: %@"; +/* No comment provided by engineer. */ +"Files" = "File"; + /* No comment provided by engineer. */ "Files & media" = "File e multimediali"; @@ -1905,6 +1929,12 @@ /* No comment provided by engineer. */ "Forwarded from" = "Inoltrato da"; +/* snd error text */ +"Forwarding server: %@\nDestination server error: %@" = "Server di inoltro: %1$@\nErrore del server di destinazione: %2$@"; + +/* snd error text */ +"Forwarding server: %@\nError: %@" = "Server di inoltro: %1$@\nErrore: %2$@"; + /* No comment provided by engineer. */ "Found desktop" = "Desktop trovato"; @@ -2460,6 +2490,9 @@ /* No comment provided by engineer. */ "Message delivery receipts!" = "Ricevute di consegna dei messaggi!"; +/* item status text */ +"Message delivery warning" = "Avviso di consegna del messaggio"; + /* No comment provided by engineer. */ "Message draft" = "Bozza dei messaggi"; @@ -2475,6 +2508,12 @@ /* notification */ "message received" = "messaggio ricevuto"; +/* No comment provided by engineer. */ +"Message routing fallback" = "Ripiego instradamento messaggio"; + +/* No comment provided by engineer. */ +"Message routing mode" = "Modalità instradamento messaggio"; + /* No comment provided by engineer. */ "Message source remains private." = "La fonte del messaggio resta privata."; @@ -2568,9 +2607,6 @@ /* item status description */ "Most likely this connection is deleted." = "Probabilmente questa connessione è stata eliminata."; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "Probabilmente questo contatto ha eliminato la connessione con te."; - /* No comment provided by engineer. */ "Multiple chat profiles" = "Profili di chat multipli"; @@ -2589,6 +2625,9 @@ /* No comment provided by engineer. */ "Network connection" = "Connessione di rete"; +/* snd error text */ +"Network issues - message expired after many attempts to send it." = "Problemi di rete - messaggio scaduto dopo molti tentativi di inviarlo."; + /* No comment provided by engineer. */ "Network management" = "Gestione della rete"; @@ -2951,9 +2990,18 @@ /* No comment provided by engineer. */ "Private filenames" = "Nomi di file privati"; +/* No comment provided by engineer. */ +"Private message routing" = "Instradamento privato messaggi"; + +/* No comment provided by engineer. */ +"Private message routing 🚀" = "Instradamento privato dei messaggi 🚀"; + /* name of notes to self */ "Private notes" = "Note private"; +/* No comment provided by engineer. */ +"Private routing" = "Instradamento privato"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profilo e connessioni al server"; @@ -3005,9 +3053,15 @@ /* No comment provided by engineer. */ "Protect app screen" = "Proteggi la schermata dell'app"; +/* No comment provided by engineer. */ +"Protect IP address" = "Proteggi l'indirizzo IP"; + /* No comment provided by engineer. */ "Protect your chat profiles with a password!" = "Proteggi i tuoi profili di chat con una password!"; +/* No comment provided by engineer. */ +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Proteggi il tuo indirizzo IP dai relay di messaggistica scelti dai tuoi contatti.\nAttivalo nelle impostazioni *Rete e server*."; + /* No comment provided by engineer. */ "Protocol timeout" = "Scadenza del protocollo"; @@ -3077,9 +3131,6 @@ /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "L'indirizzo di ricezione verrà cambiato in un server diverso. La modifica dell'indirizzo verrà completata dopo che il mittente sarà in linea."; -/* No comment provided by engineer. */ -"Receiving concurrency" = "Ricezione concomitanza"; - /* No comment provided by engineer. */ "Receiving file will be stopped." = "La ricezione del file verrà interrotta."; @@ -3236,6 +3287,9 @@ /* No comment provided by engineer. */ "Run chat" = "Avvia chat"; +/* No comment provided by engineer. */ +"Safely receive files" = "Ricevi i file in sicurezza"; + /* No comment provided by engineer. */ "Safer groups" = "Gruppi più sicuri"; @@ -3392,6 +3446,12 @@ /* No comment provided by engineer. */ "Send live message" = "Invia messaggio in diretta"; +/* No comment provided by engineer. */ +"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Invia messaggi direttamente quando l'indirizzo IP è protetto e il tuo server o quello di destinazione non supporta l'instradamento privato."; + +/* No comment provided by engineer. */ +"Send messages directly when your or destination server does not support private routing." = "Invia messaggi direttamente quando il tuo server o quello di destinazione non supporta l'instradamento privato."; + /* No comment provided by engineer. */ "Send notifications" = "Invia notifiche"; @@ -3455,6 +3515,9 @@ /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "I messaggi inviati verranno eliminati dopo il tempo impostato."; +/* srv error text. */ +"Server address is incompatible with network settings." = "L'indirizzo del server non è compatibile con le impostazioni di rete."; + /* server test error */ "Server requires authorization to create queues, check password" = "Il server richiede l'autorizzazione di creare code, controlla la password"; @@ -3464,6 +3527,9 @@ /* No comment provided by engineer. */ "Server test failed!" = "Test del server fallito!"; +/* srv error text */ +"Server version is incompatible with network settings." = "La versione del server non è compatibile con le impostazioni di rete."; + /* No comment provided by engineer. */ "Servers" = "Server"; @@ -3530,6 +3596,9 @@ /* No comment provided by engineer. */ "Share with contacts" = "Condividi con i contatti"; +/* No comment provided by engineer. */ +"Show → on messages sent via private routing." = "Mostra → nei messaggi inviati via instradamento privato."; + /* No comment provided by engineer. */ "Show calls in phone history" = "Mostra le chiamate nella cronologia del telefono"; @@ -3539,6 +3608,9 @@ /* No comment provided by engineer. */ "Show last messages" = "Mostra ultimi messaggi"; +/* No comment provided by engineer. */ +"Show message status" = "Mostra stato del messaggio"; + /* No comment provided by engineer. */ "Show preview" = "Mostra anteprima"; @@ -3746,6 +3818,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "L'app può avvisarti quando ricevi messaggi o richieste di contatto: apri le impostazioni per attivare."; +/* No comment provided by engineer. */ +"The app will ask to confirm downloads from unknown file servers (except .onion)." = "L'app chiederà di confermare i download da server di file sconosciuti (eccetto .onion)."; + /* No comment provided by engineer. */ "The attempt to change database passphrase was not completed." = "Il tentativo di cambiare la password del database non è stato completato."; @@ -3866,6 +3941,9 @@ /* 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." = "Per proteggere le tue informazioni, attiva SimpleX Lock.\nTi verrà chiesto di completare l'autenticazione prima di attivare questa funzionalità."; +/* No comment provided by engineer. */ +"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Per proteggere il tuo indirizzo IP, l'instradamento privato usa i tuoi server SMP per consegnare i messaggi."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Per registrare un messaggio vocale, concedi l'autorizzazione all'uso del microfono."; @@ -3920,9 +3998,6 @@ /* rcv group event chat item */ "unblocked %@" = "ha sbloccato %@"; -/* item status description */ -"Unexpected error: %@" = "Errore imprevisto: % @"; - /* No comment provided by engineer. */ "Unexpected migration state" = "Stato di migrazione imprevisto"; @@ -3953,6 +4028,12 @@ /* No comment provided by engineer. */ "Unknown error" = "Errore sconosciuto"; +/* No comment provided by engineer. */ +"unknown relays" = "relay sconosciuti"; + +/* No comment provided by engineer. */ +"Unknown servers!" = "Server sconosciuti!"; + /* No comment provided by engineer. */ "unknown status" = "stato sconosciuto"; @@ -3977,6 +4058,9 @@ /* No comment provided by engineer. */ "Unmute" = "Riattiva notifiche"; +/* No comment provided by engineer. */ +"unprotected" = "non protetto"; + /* No comment provided by engineer. */ "Unread" = "Non letto"; @@ -4046,6 +4130,12 @@ /* No comment provided by engineer. */ "Use only local notifications?" = "Usare solo notifiche locali?"; +/* No comment provided by engineer. */ +"Use private routing with unknown servers when IP address is not protected." = "Usa l'instradamento privato con server sconosciuti quando l'indirizzo IP non è protetto."; + +/* No comment provided by engineer. */ +"Use private routing with unknown servers." = "Usa l'instradamento privato con server sconosciuti."; + /* No comment provided by engineer. */ "Use server" = "Usa il server"; @@ -4199,6 +4289,9 @@ /* No comment provided by engineer. */ "When connecting audio and video calls." = "Quando si connettono le chiamate audio e video."; +/* No comment provided by engineer. */ +"when IP hidden" = "quando l'IP è nascosto"; + /* No comment provided by engineer. */ "When people request to connect, you can accept or reject it." = "Quando le persone chiedono di connettersi, puoi accettare o rifiutare."; @@ -4223,9 +4316,18 @@ /* No comment provided by engineer. */ "With reduced battery usage." = "Con consumo di batteria ridotto."; +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to file servers." = "Senza Tor o VPN, il tuo indirizzo IP sarà visibile ai server di file."; + +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Senza Tor o VPN, il tuo indirizzo IP sarà visibile a questi relay XFTP: %@."; + /* No comment provided by engineer. */ "Wrong database passphrase" = "Password del database sbagliata"; +/* snd error text */ +"Wrong key or unknown connection - most likely this connection is deleted." = "Chiave sbagliata o connessione sconosciuta - molto probabilmente questa connessione è stata eliminata."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "Password sbagliata!"; diff --git a/apps/ios/ja.lproj/Localizable.strings b/apps/ios/ja.lproj/Localizable.strings index f8bacda7b8..bfb6ef4a3a 100644 --- a/apps/ios/ja.lproj/Localizable.strings +++ b/apps/ios/ja.lproj/Localizable.strings @@ -1509,7 +1509,7 @@ /* No comment provided by engineer. */ "Error: " = "エラー : "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "エラー : %@"; /* No comment provided by engineer. */ @@ -2130,9 +2130,6 @@ /* item status description */ "Most likely this connection is deleted." = "おそらく、この接続は削除されています。"; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "恐らくこの連絡先があなたとの接続を削除しました。"; - /* No comment provided by engineer. */ "Multiple chat profiles" = "複数チャットのプロフィール"; @@ -3191,9 +3188,6 @@ /* No comment provided by engineer. */ "Unable to record voice message" = "音声メッセージを録音できません"; -/* item status description */ -"Unexpected error: %@" = "予期しないエラー: %@"; - /* No comment provided by engineer. */ "Unexpected migration state" = "予期しない移行状態"; diff --git a/apps/ios/nl.lproj/Localizable.strings b/apps/ios/nl.lproj/Localizable.strings index e12b8058f6..119cbc6378 100644 --- a/apps/ios/nl.lproj/Localizable.strings +++ b/apps/ios/nl.lproj/Localizable.strings @@ -449,6 +449,9 @@ /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Sta verdwijnende berichten alleen toe als uw contact dit toestaat."; +/* No comment provided by engineer. */ +"Allow downgrade" = "Downgraden toestaan"; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Sta het onomkeerbaar verwijderen van berichten alleen toe als uw contact dit toestaat. (24 uur)"; @@ -509,6 +512,9 @@ /* pref value */ "always" = "altijd"; +/* No comment provided by engineer. */ +"Always use private routing." = "Gebruik altijd privéroutering."; + /* No comment provided by engineer. */ "Always use relay" = "Altijd relay gebruiken"; @@ -716,6 +722,9 @@ /* No comment provided by engineer. */ "Cannot receive file" = "Kan bestand niet ontvangen"; +/* snd error text */ +"Capacity exceeded - recipient did not receive previously sent messages." = "Capaciteit overschreden - ontvanger heeft eerder verzonden berichten niet ontvangen."; + /* No comment provided by engineer. */ "Cellular" = "Mobiel"; @@ -852,6 +861,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Bevestig database upgrades"; +/* No comment provided by engineer. */ +"Confirm files from unknown servers." = "Bevestig bestanden van onbekende servers."; + /* No comment provided by engineer. */ "Confirm network settings" = "Bevestig netwerk instellingen"; @@ -1320,6 +1332,9 @@ /* No comment provided by engineer. */ "Desktop devices" = "Desktop apparaten"; +/* snd error text */ +"Destination server error: %@" = "Bestemmingsserverfout: %@"; + /* No comment provided by engineer. */ "Develop" = "Ontwikkelen"; @@ -1398,6 +1413,12 @@ /* No comment provided by engineer. */ "Do not send history to new members." = "Stuur geen geschiedenis naar nieuwe leden."; +/* No comment provided by engineer. */ +"Do NOT send messages directly, even if your or destination server does not support private routing." = "Stuur GEEN berichten rechtstreeks, zelfs als uw of de bestemmingsserver geen privéroutering ondersteunt."; + +/* No comment provided by engineer. */ +"Do NOT use private routing." = "Gebruik GEEN privéroutering."; + /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "Gebruik SimpleX NIET voor noodoproepen."; @@ -1779,7 +1800,7 @@ /* No comment provided by engineer. */ "Error: " = "Fout: "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "Fout: %@"; /* No comment provided by engineer. */ @@ -1839,6 +1860,9 @@ /* No comment provided by engineer. */ "File: %@" = "Bestand: %@"; +/* No comment provided by engineer. */ +"Files" = "Bestanden"; + /* No comment provided by engineer. */ "Files & media" = "Bestanden en media"; @@ -1905,6 +1929,12 @@ /* No comment provided by engineer. */ "Forwarded from" = "Doorgestuurd vanuit"; +/* snd error text */ +"Forwarding server: %@\nDestination server error: %@" = "Doorstuurserver: %1$@\nBestemmingsserverfout: %2$@"; + +/* snd error text */ +"Forwarding server: %@\nError: %@" = "Doorstuurserver: %1$@\nFout: %2$@"; + /* No comment provided by engineer. */ "Found desktop" = "Desktop gevonden"; @@ -2460,6 +2490,9 @@ /* No comment provided by engineer. */ "Message delivery receipts!" = "Ontvangst bevestiging voor berichten!"; +/* item status text */ +"Message delivery warning" = "Waarschuwing voor berichtbezorging"; + /* No comment provided by engineer. */ "Message draft" = "Concept bericht"; @@ -2475,6 +2508,12 @@ /* notification */ "message received" = "bericht ontvangen"; +/* No comment provided by engineer. */ +"Message routing fallback" = "Terugval op berichtroutering"; + +/* No comment provided by engineer. */ +"Message routing mode" = "Berichtrouteringsmodus"; + /* No comment provided by engineer. */ "Message source remains private." = "Berichtbron blijft privé."; @@ -2568,9 +2607,6 @@ /* item status description */ "Most likely this connection is deleted." = "Hoogstwaarschijnlijk is deze verbinding verwijderd."; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "Hoogstwaarschijnlijk heeft dit contact de verbinding met jou verwijderd."; - /* No comment provided by engineer. */ "Multiple chat profiles" = "Meerdere chat profielen"; @@ -2589,6 +2625,9 @@ /* No comment provided by engineer. */ "Network connection" = "Netwerkverbinding"; +/* snd error text */ +"Network issues - message expired after many attempts to send it." = "Netwerkproblemen - bericht is verlopen na vele pogingen om het te verzenden."; + /* No comment provided by engineer. */ "Network management" = "Netwerkbeheer"; @@ -2951,9 +2990,18 @@ /* No comment provided by engineer. */ "Private filenames" = "Privé bestandsnamen"; +/* No comment provided by engineer. */ +"Private message routing" = "Routering van privéberichten"; + +/* No comment provided by engineer. */ +"Private message routing 🚀" = "Routing van privéberichten🚀"; + /* name of notes to self */ "Private notes" = "Privé notities"; +/* No comment provided by engineer. */ +"Private routing" = "Privéroutering"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profiel- en serververbindingen"; @@ -3005,9 +3053,15 @@ /* No comment provided by engineer. */ "Protect app screen" = "App scherm verbergen"; +/* No comment provided by engineer. */ +"Protect IP address" = "Bescherm het IP-adres"; + /* No comment provided by engineer. */ "Protect your chat profiles with a password!" = "Bescherm je chat profielen met een wachtwoord!"; +/* No comment provided by engineer. */ +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Bescherm uw IP-adres tegen de berichtenrelais die door uw contacten zijn gekozen.\nSchakel dit in in *Netwerk en servers*-instellingen."; + /* No comment provided by engineer. */ "Protocol timeout" = "Protocol timeout"; @@ -3077,9 +3131,6 @@ /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Het ontvangstadres wordt gewijzigd naar een andere server. Adres wijziging wordt voltooid nadat de afzender online is."; -/* No comment provided by engineer. */ -"Receiving concurrency" = "Gelijktijdig ontvangen"; - /* No comment provided by engineer. */ "Receiving file will be stopped." = "Het ontvangen van het bestand wordt gestopt."; @@ -3236,6 +3287,9 @@ /* No comment provided by engineer. */ "Run chat" = "Chat uitvoeren"; +/* No comment provided by engineer. */ +"Safely receive files" = "Veilig bestanden ontvangen"; + /* No comment provided by engineer. */ "Safer groups" = "Veiligere groepen"; @@ -3392,6 +3446,12 @@ /* No comment provided by engineer. */ "Send live message" = "Stuur een livebericht"; +/* No comment provided by engineer. */ +"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Stuur berichten rechtstreeks als het IP-adres beschermd is en uw of bestemmingsserver geen privéroutering ondersteunt."; + +/* No comment provided by engineer. */ +"Send messages directly when your or destination server does not support private routing." = "Stuur berichten rechtstreeks wanneer uw of de doelserver geen privéroutering ondersteunt."; + /* No comment provided by engineer. */ "Send notifications" = "Meldingen verzenden"; @@ -3455,6 +3515,9 @@ /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "Verzonden berichten worden na ingestelde tijd verwijderd."; +/* srv error text. */ +"Server address is incompatible with network settings." = "Serveradres is niet compatibel met netwerkinstellingen."; + /* server test error */ "Server requires authorization to create queues, check password" = "Server vereist autorisatie om wachtrijen te maken, controleer wachtwoord"; @@ -3464,6 +3527,9 @@ /* No comment provided by engineer. */ "Server test failed!" = "Servertest mislukt!"; +/* srv error text */ +"Server version is incompatible with network settings." = "Serverversie is incompatibel met netwerkinstellingen."; + /* No comment provided by engineer. */ "Servers" = "Servers"; @@ -3530,6 +3596,9 @@ /* No comment provided by engineer. */ "Share with contacts" = "Delen met contacten"; +/* No comment provided by engineer. */ +"Show → on messages sent via private routing." = "Toon → bij berichten verzonden via privéroutering."; + /* No comment provided by engineer. */ "Show calls in phone history" = "Toon oproepen in de telefoongeschiedenis"; @@ -3539,6 +3608,9 @@ /* No comment provided by engineer. */ "Show last messages" = "Laat laatste berichten zien"; +/* No comment provided by engineer. */ +"Show message status" = "Toon berichtstatus"; + /* No comment provided by engineer. */ "Show preview" = "Toon voorbeeld"; @@ -3746,6 +3818,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "De app kan u op de hoogte stellen wanneer u berichten of contact verzoeken ontvangt - open de instellingen om dit in te schakelen."; +/* No comment provided by engineer. */ +"The app will ask to confirm downloads from unknown file servers (except .onion)." = "De app vraagt om downloads van onbekende bestandsservers (behalve .onion) te bevestigen."; + /* No comment provided by engineer. */ "The attempt to change database passphrase was not completed." = "De poging om het wachtwoord van de database te wijzigen is niet voltooid."; @@ -3866,6 +3941,9 @@ /* 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." = "Schakel SimpleX Vergrendelen om uw informatie te beschermen.\nU wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingeschakeld."; +/* No comment provided by engineer. */ +"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Om uw IP-adres te beschermen, gebruikt privéroutering uw SMP-servers om berichten te bezorgen."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Geef toestemming om de microfoon te gebruiken om een spraakbericht op te nemen."; @@ -3920,9 +3998,6 @@ /* rcv group event chat item */ "unblocked %@" = "gedeblokkeerd %@"; -/* item status description */ -"Unexpected error: %@" = "Onverwachte fout: %@"; - /* No comment provided by engineer. */ "Unexpected migration state" = "Onverwachte migratiestatus"; @@ -3953,6 +4028,12 @@ /* No comment provided by engineer. */ "Unknown error" = "Onbekende fout"; +/* No comment provided by engineer. */ +"unknown relays" = "onbekende relays"; + +/* No comment provided by engineer. */ +"Unknown servers!" = "Onbekende servers!"; + /* No comment provided by engineer. */ "unknown status" = "onbekende status"; @@ -3977,6 +4058,9 @@ /* No comment provided by engineer. */ "Unmute" = "Dempen opheffen"; +/* No comment provided by engineer. */ +"unprotected" = "onbeschermd"; + /* No comment provided by engineer. */ "Unread" = "Ongelezen"; @@ -4046,6 +4130,12 @@ /* No comment provided by engineer. */ "Use only local notifications?" = "Alleen lokale meldingen gebruiken?"; +/* No comment provided by engineer. */ +"Use private routing with unknown servers when IP address is not protected." = "Gebruik privéroutering met onbekende servers wanneer het IP-adres niet beveiligd is."; + +/* No comment provided by engineer. */ +"Use private routing with unknown servers." = "Gebruik privéroutering met onbekende servers."; + /* No comment provided by engineer. */ "Use server" = "Gebruik server"; @@ -4199,6 +4289,9 @@ /* No comment provided by engineer. */ "When connecting audio and video calls." = "Bij het verbinden van audio- en video-oproepen."; +/* No comment provided by engineer. */ +"when IP hidden" = "wanneer IP verborgen is"; + /* No comment provided by engineer. */ "When people request to connect, you can accept or reject it." = "Wanneer mensen vragen om verbinding te maken, kunt u dit accepteren of weigeren."; @@ -4223,9 +4316,18 @@ /* No comment provided by engineer. */ "With reduced battery usage." = "Met verminderd batterijgebruik."; +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to file servers." = "Zonder Tor of VPN is uw IP-adres zichtbaar voor bestandsservers."; + +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Zonder Tor of VPN zal uw IP-adres zichtbaar zijn voor deze XFTP-relays: %@."; + /* No comment provided by engineer. */ "Wrong database passphrase" = "Verkeerd wachtwoord voor de database"; +/* snd error text */ +"Wrong key or unknown connection - most likely this connection is deleted." = "Verkeerde sleutel of onbekende verbinding - hoogstwaarschijnlijk is deze verbinding verwijderd."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "Verkeerd wachtwoord!"; diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings index 15262085eb..3189c74cf2 100644 --- a/apps/ios/pl.lproj/Localizable.strings +++ b/apps/ios/pl.lproj/Localizable.strings @@ -449,6 +449,9 @@ /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Zezwól na znikające wiadomości tylko wtedy, gdy Twój kontakt Ci na to pozwoli."; +/* No comment provided by engineer. */ +"Allow downgrade" = "Zezwól na obniżenie wersji"; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Zezwalaj na nieodwracalne usuwanie wiadomości tylko wtedy, gdy Twój kontakt Ci na to pozwoli. (24 godziny)"; @@ -509,6 +512,9 @@ /* pref value */ "always" = "zawsze"; +/* No comment provided by engineer. */ +"Always use private routing." = "Zawsze używaj prywatnego trasowania."; + /* No comment provided by engineer. */ "Always use relay" = "Zawsze używaj przekaźnika"; @@ -716,6 +722,9 @@ /* No comment provided by engineer. */ "Cannot receive file" = "Nie można odebrać pliku"; +/* snd error text */ +"Capacity exceeded - recipient did not receive previously sent messages." = "Przekroczono pojemność - odbiorca nie otrzymał wcześniej wysłanych wiadomości."; + /* No comment provided by engineer. */ "Cellular" = "Sieć komórkowa"; @@ -852,6 +861,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Potwierdź aktualizacje bazy danych"; +/* No comment provided by engineer. */ +"Confirm files from unknown servers." = "Potwierdzaj pliki z nieznanych serwerów."; + /* No comment provided by engineer. */ "Confirm network settings" = "Potwierdź ustawienia sieciowe"; @@ -1320,6 +1332,9 @@ /* No comment provided by engineer. */ "Desktop devices" = "Urządzenia komputerowe"; +/* snd error text */ +"Destination server error: %@" = "Błąd docelowego serwera: %@"; + /* No comment provided by engineer. */ "Develop" = "Deweloperskie"; @@ -1398,6 +1413,12 @@ /* No comment provided by engineer. */ "Do not send history to new members." = "Nie wysyłaj historii do nowych członków."; +/* No comment provided by engineer. */ +"Do NOT send messages directly, even if your or destination server does not support private routing." = "NIE wysyłaj wiadomości bezpośrednio, nawet jeśli serwer docelowy nie obsługuje prywatnego trasowania."; + +/* No comment provided by engineer. */ +"Do NOT use private routing." = "NIE używaj prywatnego trasowania."; + /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "NIE używaj SimpleX do połączeń alarmowych."; @@ -1779,7 +1800,7 @@ /* No comment provided by engineer. */ "Error: " = "Błąd: "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "Błąd: %@"; /* No comment provided by engineer. */ @@ -1839,6 +1860,9 @@ /* No comment provided by engineer. */ "File: %@" = "Plik: %@"; +/* No comment provided by engineer. */ +"Files" = "Pliki"; + /* No comment provided by engineer. */ "Files & media" = "Pliki i media"; @@ -1905,6 +1929,12 @@ /* No comment provided by engineer. */ "Forwarded from" = "Przekazane dalej od"; +/* snd error text */ +"Forwarding server: %@\nDestination server error: %@" = "Serwer przekazujący: %1$@\nBłąd serwera docelowego: %2$@"; + +/* snd error text */ +"Forwarding server: %@\nError: %@" = "Serwer przekazujący: %1$@\nBłąd: %2$@"; + /* No comment provided by engineer. */ "Found desktop" = "Znaleziono komputer"; @@ -2460,6 +2490,9 @@ /* No comment provided by engineer. */ "Message delivery receipts!" = "Potwierdzenia dostarczenia wiadomości!"; +/* item status text */ +"Message delivery warning" = "Ostrzeżenie dostarczenia wiadomości"; + /* No comment provided by engineer. */ "Message draft" = "Wersja robocza wiadomości"; @@ -2475,6 +2508,12 @@ /* notification */ "message received" = "wiadomość otrzymana"; +/* No comment provided by engineer. */ +"Message routing fallback" = "Rezerwowe trasowania wiadomości"; + +/* No comment provided by engineer. */ +"Message routing mode" = "Tryb trasowania wiadomości"; + /* No comment provided by engineer. */ "Message source remains private." = "Źródło wiadomości pozostaje prywatne."; @@ -2568,9 +2607,6 @@ /* item status description */ "Most likely this connection is deleted." = "Najprawdopodobniej to połączenie jest usunięte."; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "Najprawdopodobniej ten kontakt usunął połączenie z Tobą."; - /* No comment provided by engineer. */ "Multiple chat profiles" = "Wiele profili czatu"; @@ -2589,6 +2625,9 @@ /* No comment provided by engineer. */ "Network connection" = "Połączenie z siecią"; +/* snd error text */ +"Network issues - message expired after many attempts to send it." = "Błąd sieciowy - wiadomość wygasła po wielu próbach wysłania jej."; + /* No comment provided by engineer. */ "Network management" = "Zarządzenie sieciowe"; @@ -2951,9 +2990,18 @@ /* No comment provided by engineer. */ "Private filenames" = "Prywatne nazwy plików"; +/* No comment provided by engineer. */ +"Private message routing" = "Trasowanie prywatnych wiadomości"; + +/* No comment provided by engineer. */ +"Private message routing 🚀" = "Trasowanie prywatnych wiadomości🚀"; + /* name of notes to self */ "Private notes" = "Prywatne notatki"; +/* No comment provided by engineer. */ +"Private routing" = "Prywatne trasowanie"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profil i połączenia z serwerem"; @@ -3005,9 +3053,15 @@ /* No comment provided by engineer. */ "Protect app screen" = "Chroń ekran aplikacji"; +/* No comment provided by engineer. */ +"Protect IP address" = "Chroń adres IP"; + /* No comment provided by engineer. */ "Protect your chat profiles with a password!" = "Chroń swoje profile czatu hasłem!"; +/* No comment provided by engineer. */ +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Chroni Twój adres IP przed przekaźnikami wiadomości wybranych przez Twoje kontakty.\nWłącz w ustawianiach *Sieć i serwery* ."; + /* No comment provided by engineer. */ "Protocol timeout" = "Limit czasu protokołu"; @@ -3077,9 +3131,6 @@ /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Adres odbiorczy zostanie zmieniony na inny serwer. Zmiana adresu zostanie zakończona gdy nadawca będzie online."; -/* No comment provided by engineer. */ -"Receiving concurrency" = "Konkurencyjne odbieranie"; - /* No comment provided by engineer. */ "Receiving file will be stopped." = "Odbieranie pliku zostanie przerwane."; @@ -3236,6 +3287,9 @@ /* No comment provided by engineer. */ "Run chat" = "Uruchom czat"; +/* No comment provided by engineer. */ +"Safely receive files" = "Bezpiecznie otrzymuj pliki"; + /* No comment provided by engineer. */ "Safer groups" = "Bezpieczniejsze grupy"; @@ -3392,6 +3446,12 @@ /* No comment provided by engineer. */ "Send live message" = "Wyślij wiadomość na żywo"; +/* No comment provided by engineer. */ +"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Wysyłaj wiadomości bezpośrednio, gdy adres IP jest chroniony i Twój lub docelowy serwer nie obsługuje prywatnego trasowania."; + +/* No comment provided by engineer. */ +"Send messages directly when your or destination server does not support private routing." = "Wysyłaj wiadomości bezpośrednio, gdy Twój lub docelowy serwer nie obsługuje prywatnego trasowania."; + /* No comment provided by engineer. */ "Send notifications" = "Wyślij powiadomienia"; @@ -3455,6 +3515,9 @@ /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "Wysłane wiadomości zostaną usunięte po ustawionym czasie."; +/* srv error text. */ +"Server address is incompatible with network settings." = "Adres serwera jest niekompatybilny z ustawieniami sieciowymi."; + /* server test error */ "Server requires authorization to create queues, check password" = "Serwer wymaga autoryzacji do tworzenia kolejek, sprawdź hasło"; @@ -3464,6 +3527,9 @@ /* No comment provided by engineer. */ "Server test failed!" = "Test serwera nie powiódł się!"; +/* srv error text */ +"Server version is incompatible with network settings." = "Wersja serwera jest niekompatybilna z ustawieniami sieciowymi."; + /* No comment provided by engineer. */ "Servers" = "Serwery"; @@ -3530,6 +3596,9 @@ /* No comment provided by engineer. */ "Share with contacts" = "Udostępnij kontaktom"; +/* No comment provided by engineer. */ +"Show → on messages sent via private routing." = "Pokaż → na wiadomościach wysłanych przez prywatne trasowanie."; + /* No comment provided by engineer. */ "Show calls in phone history" = "Pokaż połączenia w historii telefonu"; @@ -3539,6 +3608,9 @@ /* No comment provided by engineer. */ "Show last messages" = "Pokaż ostatnie wiadomości"; +/* No comment provided by engineer. */ +"Show message status" = "Pokaż status wiadomości"; + /* No comment provided by engineer. */ "Show preview" = "Pokaż podgląd"; @@ -3746,6 +3818,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Aplikacja może powiadamiać Cię, gdy otrzymujesz wiadomości lub prośby o kontakt — otwórz ustawienia, aby włączyć."; +/* No comment provided by engineer. */ +"The app will ask to confirm downloads from unknown file servers (except .onion)." = "Aplikacja zapyta o potwierdzenie pobierania od nieznanych serwerów plików (poza .onion)."; + /* No comment provided by engineer. */ "The attempt to change database passphrase was not completed." = "Próba zmiany hasła bazy danych nie została zakończona."; @@ -3866,6 +3941,9 @@ /* 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." = "Aby chronić swoje informacje, włącz funkcję blokady SimpleX.\nPrzed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania."; +/* No comment provided by engineer. */ +"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Aby chronić Twój adres IP, prywatne trasowanie używa Twoich serwerów SMP, aby dostarczyć wiadomości."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Aby nagrać wiadomość głosową należy udzielić zgody na użycie Mikrofonu."; @@ -3920,9 +3998,6 @@ /* rcv group event chat item */ "unblocked %@" = "odblokowano %@"; -/* item status description */ -"Unexpected error: %@" = "Nieoczekiwany błąd: %@"; - /* No comment provided by engineer. */ "Unexpected migration state" = "Nieoczekiwany stan migracji"; @@ -3953,6 +4028,12 @@ /* No comment provided by engineer. */ "Unknown error" = "Nieznany błąd"; +/* No comment provided by engineer. */ +"unknown relays" = "nieznane przekaźniki"; + +/* No comment provided by engineer. */ +"Unknown servers!" = "Nieznane serwery!"; + /* No comment provided by engineer. */ "unknown status" = "nieznany status"; @@ -3977,6 +4058,9 @@ /* No comment provided by engineer. */ "Unmute" = "Wyłącz wyciszenie"; +/* No comment provided by engineer. */ +"unprotected" = "niezabezpieczony"; + /* No comment provided by engineer. */ "Unread" = "Nieprzeczytane"; @@ -4046,6 +4130,12 @@ /* No comment provided by engineer. */ "Use only local notifications?" = "Używać tylko lokalnych powiadomień?"; +/* No comment provided by engineer. */ +"Use private routing with unknown servers when IP address is not protected." = "Używaj prywatnego trasowania z nieznanymi serwerami, gdy adres IP nie jest chroniony."; + +/* No comment provided by engineer. */ +"Use private routing with unknown servers." = "Używaj prywatnego trasowania z nieznanymi serwerami."; + /* No comment provided by engineer. */ "Use server" = "Użyj serwera"; @@ -4199,6 +4289,9 @@ /* No comment provided by engineer. */ "When connecting audio and video calls." = "Podczas łączenia połączeń audio i wideo."; +/* No comment provided by engineer. */ +"when IP hidden" = "gdy IP ukryty"; + /* No comment provided by engineer. */ "When people request to connect, you can accept or reject it." = "Kiedy ludzie proszą o połączenie, możesz je zaakceptować lub odrzucić."; @@ -4223,9 +4316,18 @@ /* No comment provided by engineer. */ "With reduced battery usage." = "Ze zmniejszonym zużyciem baterii."; +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to file servers." = "Bez Tor lub VPN, Twój adres IP będzie widoczny do serwerów plików."; + +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Bez Tor lub VPN, Twój adres IP będzie widoczny dla tych przekaźników XFTP: %@."; + /* No comment provided by engineer. */ "Wrong database passphrase" = "Nieprawidłowe hasło bazy danych"; +/* snd error text */ +"Wrong key or unknown connection - most likely this connection is deleted." = "Zły klucz lub nieznane połączenie - najprawdopodobniej to połączenie jest usunięte."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "Nieprawidłowe hasło!"; diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings index 2c703f0095..c0ad6e23cf 100644 --- a/apps/ios/ru.lproj/Localizable.strings +++ b/apps/ios/ru.lproj/Localizable.strings @@ -449,6 +449,9 @@ /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Разрешить исчезающие сообщения, только если Ваш контакт разрешает их Вам."; +/* No comment provided by engineer. */ +"Allow downgrade" = "Разрешить прямую доставку"; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Разрешить необратимое удаление сообщений, только если Ваш контакт разрешает это Вам. (24 часа)"; @@ -509,6 +512,9 @@ /* pref value */ "always" = "всегда"; +/* No comment provided by engineer. */ +"Always use private routing." = "Всегда использовать конфиденциальную доставку."; + /* No comment provided by engineer. */ "Always use relay" = "Всегда соединяться через relay"; @@ -716,6 +722,9 @@ /* No comment provided by engineer. */ "Cannot receive file" = "Невозможно получить файл"; +/* snd error text */ +"Capacity exceeded - recipient did not receive previously sent messages." = "Превышено количество сообщений - предыдущие сообщения не доставлены."; + /* No comment provided by engineer. */ "Cellular" = "Мобильная сеть"; @@ -852,6 +861,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Подтвердить обновление базы данных"; +/* No comment provided by engineer. */ +"Confirm files from unknown servers." = "Подтверждать файлы с неизвестных серверов."; + /* No comment provided by engineer. */ "Confirm network settings" = "Подтвердите настройки сети"; @@ -1320,6 +1332,9 @@ /* No comment provided by engineer. */ "Desktop devices" = "Компьютеры"; +/* snd error text */ +"Destination server error: %@" = "Ошибка сервера получателя: %@"; + /* No comment provided by engineer. */ "Develop" = "Для разработчиков"; @@ -1398,6 +1413,12 @@ /* No comment provided by engineer. */ "Do not send history to new members." = "Не отправлять историю новым членам."; +/* No comment provided by engineer. */ +"Do NOT send messages directly, even if your or destination server does not support private routing." = "Не отправлять сообщения напрямую, даже если сервер получателя не поддерживает конфиденциальную доставку."; + +/* No comment provided by engineer. */ +"Do NOT use private routing." = "Не использовать конфиденциальную доставку."; + /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "Не используйте SimpleX для экстренных звонков."; @@ -1779,7 +1800,7 @@ /* No comment provided by engineer. */ "Error: " = "Ошибка: "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "Ошибка: %@"; /* No comment provided by engineer. */ @@ -1839,6 +1860,9 @@ /* No comment provided by engineer. */ "File: %@" = "Файл: %@"; +/* No comment provided by engineer. */ +"Files" = "Файлы"; + /* No comment provided by engineer. */ "Files & media" = "Файлы и медиа"; @@ -1905,6 +1929,12 @@ /* No comment provided by engineer. */ "Forwarded from" = "Переслано из"; +/* snd error text */ +"Forwarding server: %@\nDestination server error: %@" = "Пересылающий сервер: %1$@\nОшибка сервера получателя: %2$@"; + +/* snd error text */ +"Forwarding server: %@\nError: %@" = "Пересылающий сервер: %1$@\nОшибка: %2$@"; + /* No comment provided by engineer. */ "Found desktop" = "Компьютер найден"; @@ -2460,6 +2490,9 @@ /* No comment provided by engineer. */ "Message delivery receipts!" = "Отчеты о доставке сообщений!"; +/* item status text */ +"Message delivery warning" = "Предупреждение доставки сообщения"; + /* No comment provided by engineer. */ "Message draft" = "Черновик сообщения"; @@ -2475,6 +2508,12 @@ /* notification */ "message received" = "получено сообщение"; +/* No comment provided by engineer. */ +"Message routing fallback" = "Прямая доставка сообщений"; + +/* No comment provided by engineer. */ +"Message routing mode" = "Режим доставки сообщений"; + /* No comment provided by engineer. */ "Message source remains private." = "Источник сообщения остаётся конфиденциальным."; @@ -2568,9 +2607,6 @@ /* item status description */ "Most likely this connection is deleted." = "Скорее всего, соединение удалено."; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "Скорее всего, этот контакт удалил соединение с Вами."; - /* No comment provided by engineer. */ "Multiple chat profiles" = "Много профилей чата"; @@ -2589,6 +2625,9 @@ /* No comment provided by engineer. */ "Network connection" = "Интернет-соединение"; +/* snd error text */ +"Network issues - message expired after many attempts to send it." = "Ошибка сети - сообщение не было отправлено после многократных попыток."; + /* No comment provided by engineer. */ "Network management" = "Статус сети"; @@ -2951,9 +2990,18 @@ /* No comment provided by engineer. */ "Private filenames" = "Защищенные имена файлов"; +/* No comment provided by engineer. */ +"Private message routing" = "Конфиденциальная доставка сообщений"; + +/* No comment provided by engineer. */ +"Private message routing 🚀" = "Конфиденциальная доставка сообщений 🚀"; + /* name of notes to self */ "Private notes" = "Личные заметки"; +/* No comment provided by engineer. */ +"Private routing" = "Конфиденциальная доставка"; + /* No comment provided by engineer. */ "Profile and server connections" = "Профиль и соединения на сервере"; @@ -3005,9 +3053,15 @@ /* No comment provided by engineer. */ "Protect app screen" = "Защитить экран приложения"; +/* No comment provided by engineer. */ +"Protect IP address" = "Защитить IP адрес"; + /* No comment provided by engineer. */ "Protect your chat profiles with a password!" = "Защитите Ваши профили чата паролем!"; +/* No comment provided by engineer. */ +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Защитите ваш IP адрес от серверов сообщений, выбранных Вашими контактами.\nВключите в настройках *Сеть и серверы*."; + /* No comment provided by engineer. */ "Protocol timeout" = "Таймаут протокола"; @@ -3077,9 +3131,6 @@ /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Адрес получения сообщений будет перемещён на другой сервер. Изменение адреса завершится после того как отправитель будет онлайн."; -/* No comment provided by engineer. */ -"Receiving concurrency" = "Одновременный приём"; - /* No comment provided by engineer. */ "Receiving file will be stopped." = "Приём файла будет прекращён."; @@ -3236,6 +3287,9 @@ /* No comment provided by engineer. */ "Run chat" = "Запустить chat"; +/* No comment provided by engineer. */ +"Safely receive files" = "Получайте файлы безопасно"; + /* No comment provided by engineer. */ "Safer groups" = "Более безопасные группы"; @@ -3392,6 +3446,12 @@ /* No comment provided by engineer. */ "Send live message" = "Отправить живое сообщение"; +/* No comment provided by engineer. */ +"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Отправлять сообщения напрямую, когда IP адрес защищен, и Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку."; + +/* No comment provided by engineer. */ +"Send messages directly when your or destination server does not support private routing." = "Отправлять сообщения напрямую, когда Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку."; + /* No comment provided by engineer. */ "Send notifications" = "Отправлять уведомления"; @@ -3455,6 +3515,9 @@ /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "Отправленные сообщения будут удалены через заданное время."; +/* srv error text. */ +"Server address is incompatible with network settings." = "Адрес сервера несовместим с настройками сети."; + /* server test error */ "Server requires authorization to create queues, check password" = "Сервер требует авторизации для создания очередей, проверьте пароль"; @@ -3464,6 +3527,9 @@ /* No comment provided by engineer. */ "Server test failed!" = "Ошибка теста сервера!"; +/* srv error text */ +"Server version is incompatible with network settings." = "Версия сервера несовместима с настройками сети."; + /* No comment provided by engineer. */ "Servers" = "Серверы"; @@ -3530,6 +3596,9 @@ /* No comment provided by engineer. */ "Share with contacts" = "Поделиться с контактами"; +/* No comment provided by engineer. */ +"Show → on messages sent via private routing." = "Показать → на сообщениях доставленных конфиденциально."; + /* No comment provided by engineer. */ "Show calls in phone history" = "Показать звонки в истории телефона"; @@ -3539,6 +3608,9 @@ /* No comment provided by engineer. */ "Show last messages" = "Показывать последние сообщения"; +/* No comment provided by engineer. */ +"Show message status" = "Показать статус сообщения"; + /* No comment provided by engineer. */ "Show preview" = "Показывать уведомления"; @@ -3746,6 +3818,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Приложение может посылать Вам уведомления о сообщениях и запросах на соединение - уведомления можно включить в Настройках."; +/* No comment provided by engineer. */ +"The app will ask to confirm downloads from unknown file servers (except .onion)." = "Приложение будет запрашивать подтверждение загрузки с неизвестных серверов (за исключением .onion адресов)."; + /* No comment provided by engineer. */ "The attempt to change database passphrase was not completed." = "Попытка поменять пароль базы данных не была завершена."; @@ -3866,6 +3941,9 @@ /* No comment provided by engineer. */ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "Чтобы защитить Вашу информацию, включите блокировку SimpleX Chat.\nВам будет нужно пройти аутентификацию для включения блокировки."; +/* No comment provided by engineer. */ +"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Чтобы защитить ваш IP адрес, приложение использует Ваши SMP серверы для конфиденциальной доставки сообщений."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Для записи голосового сообщения, пожалуйста разрешите доступ к микрофону."; @@ -3920,9 +3998,6 @@ /* rcv group event chat item */ "unblocked %@" = "%@ разблокирован"; -/* item status description */ -"Unexpected error: %@" = "Неожиданная ошибка: %@"; - /* No comment provided by engineer. */ "Unexpected migration state" = "Неожиданная ошибка при перемещении данных чата"; @@ -3953,6 +4028,12 @@ /* No comment provided by engineer. */ "Unknown error" = "Неизвестная ошибка"; +/* No comment provided by engineer. */ +"unknown relays" = "неизвестные серверы"; + +/* No comment provided by engineer. */ +"Unknown servers!" = "Неизвестные серверы!"; + /* No comment provided by engineer. */ "unknown status" = "неизвестный статус"; @@ -3977,6 +4058,9 @@ /* No comment provided by engineer. */ "Unmute" = "Уведомлять"; +/* No comment provided by engineer. */ +"unprotected" = "незащищённый"; + /* No comment provided by engineer. */ "Unread" = "Не прочитано"; @@ -4046,6 +4130,12 @@ /* No comment provided by engineer. */ "Use only local notifications?" = "Использовать только локальные нотификации?"; +/* No comment provided by engineer. */ +"Use private routing with unknown servers when IP address is not protected." = "Использовать конфиденциальную доставку с неизвестными серверами, когда IP адрес не защищен."; + +/* No comment provided by engineer. */ +"Use private routing with unknown servers." = "Использовать конфиденциальную доставку с неизвестными серверами."; + /* No comment provided by engineer. */ "Use server" = "Использовать сервер"; @@ -4199,6 +4289,9 @@ /* No comment provided by engineer. */ "When connecting audio and video calls." = "Во время соединения аудио и видео звонков."; +/* No comment provided by engineer. */ +"when IP hidden" = "когда IP защищен"; + /* No comment provided by engineer. */ "When people request to connect, you can accept or reject it." = "Когда Вы получите запрос на соединение, Вы можете принять или отклонить его."; @@ -4223,9 +4316,18 @@ /* No comment provided by engineer. */ "With reduced battery usage." = "С уменьшенным потреблением батареи."; +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to file servers." = "Без Тора или ВПН, Ваш IP адрес будет доступен серверам файлов."; + +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Без Тора или ВПН, Ваш IP адрес будет доступен этим серверам файлов: %@."; + /* No comment provided by engineer. */ "Wrong database passphrase" = "Неправильный пароль базы данных"; +/* snd error text */ +"Wrong key or unknown connection - most likely this connection is deleted." = "Неверный ключ или неизвестное соединение - скорее всего, это соединение удалено."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "Неправильный пароль!"; diff --git a/apps/ios/th.lproj/Localizable.strings b/apps/ios/th.lproj/Localizable.strings index 93c56a730d..fa31dccf96 100644 --- a/apps/ios/th.lproj/Localizable.strings +++ b/apps/ios/th.lproj/Localizable.strings @@ -1386,7 +1386,7 @@ /* No comment provided by engineer. */ "Error: " = "ผิดพลาด: "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "ข้อผิดพลาด: % @"; /* No comment provided by engineer. */ @@ -1998,9 +1998,6 @@ /* No comment provided by engineer. */ "More improvements are coming soon!" = "การปรับปรุงเพิ่มเติมกำลังจะมาเร็ว ๆ นี้!"; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "เป็นไปได้มากว่าผู้ติดต่อนี้ได้ลบการเชื่อมต่อกับคุณ"; - /* No comment provided by engineer. */ "Multiple chat profiles" = "โปรไฟล์การแชทหลายรายการ"; @@ -3050,9 +3047,6 @@ /* No comment provided by engineer. */ "Unable to record voice message" = "ไม่สามารถบันทึกข้อความเสียง"; -/* item status description */ -"Unexpected error: %@" = "ข้อผิดพลาดที่ไม่คาดคิด: %@"; - /* No comment provided by engineer. */ "Unexpected migration state" = "สถานะการย้ายข้อมูลที่ไม่คาดคิด"; diff --git a/apps/ios/tr.lproj/Localizable.strings b/apps/ios/tr.lproj/Localizable.strings index 714a1bc739..696d5a3f21 100644 --- a/apps/ios/tr.lproj/Localizable.strings +++ b/apps/ios/tr.lproj/Localizable.strings @@ -275,7 +275,7 @@ "0 sec" = "0 saniye"; /* No comment provided by engineer. */ -"0s" = "0 saniye"; +"0s" = "0sn"; /* time interval */ "1 day" = "1 gün"; @@ -438,7 +438,7 @@ "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."; +"All your contacts, conversations and files will be securely encrypted and uploaded in chunks to configured XFTP relays." = "Tüm kişileriniz, konuşmalarınız ve dosyalarınız güvenli bir şekilde şifrelenir ve yapılandırılmış XFTP yönlendiricilerine parçalar halinde yüklenir."; /* No comment provided by engineer. */ "Allow" = "İzin ver"; @@ -449,6 +449,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 downgrade" = "Sürüm düşürmeye 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)"; @@ -459,7 +462,7 @@ "Allow message reactions." = "Mesaj tepkilerine izin ver."; /* No comment provided by engineer. */ -"Allow sending direct messages to members." = "Üyelere direkt mesaj göndermeye izin ver."; +"Allow sending direct messages to members." = "Üyelere doğrudan mesaj göndermeye izin ver."; /* No comment provided by engineer. */ "Allow sending disappearing messages." = "Kendiliğinden yok olan mesajlar göndermeye izin ver."; @@ -509,6 +512,9 @@ /* pref value */ "always" = "her zaman"; +/* No comment provided by engineer. */ +"Always use private routing." = "Her zaman gizli yönlendirme kullan."; + /* No comment provided by engineer. */ "Always use relay" = "Her zaman yönlendirici kullan"; @@ -716,6 +722,9 @@ /* No comment provided by engineer. */ "Cannot receive file" = "Dosya alınamıyor"; +/* snd error text */ +"Capacity exceeded - recipient did not receive previously sent messages." = "Kapasite aşıldı - alıcı önceden gönderilen mesajları almadı."; + /* No comment provided by engineer. */ "Cellular" = "Hücresel Veri"; @@ -852,6 +861,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Veritabanı geliştirmelerini onayla"; +/* No comment provided by engineer. */ +"Confirm files from unknown servers." = "Bilinmeyen sunuculardan gelen dosyaları onayla."; + /* No comment provided by engineer. */ "Confirm network settings" = "Ağ ayarlarını onaylayın"; @@ -1320,6 +1332,9 @@ /* No comment provided by engineer. */ "Desktop devices" = "Bilgisayar cihazları"; +/* snd error text */ +"Destination server error: %@" = "Hedef sunucu hatası: %@"; + /* No comment provided by engineer. */ "Develop" = "Geliştir"; @@ -1398,6 +1413,12 @@ /* 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 send messages directly, even if your or destination server does not support private routing." = "Sizin veya hedef sunucunun özel yönlendirmeyi desteklememesi durumunda bile mesajları doğrudan GÖNDERMEYİN."; + +/* No comment provided by engineer. */ +"Do NOT use private routing." = "Gizli yönlendirmeyi KULLANMA."; + /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "Acil aramalar için SimpleX'i KULLANMAYIN."; @@ -1779,7 +1800,7 @@ /* No comment provided by engineer. */ "Error: " = "Hata: "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "Hata: %@"; /* No comment provided by engineer. */ @@ -1839,6 +1860,9 @@ /* No comment provided by engineer. */ "File: %@" = "Dosya: %@"; +/* No comment provided by engineer. */ +"Files" = "Dosyalar"; + /* No comment provided by engineer. */ "Files & media" = "Dosyalar & medya"; @@ -1905,6 +1929,12 @@ /* No comment provided by engineer. */ "Forwarded from" = "Şuradan iletildi"; +/* snd error text */ +"Forwarding server: %@\nDestination server error: %@" = "Yönlendirme sunucusu: %1$@\nHedef sunucu hatası: %2$@"; + +/* snd error text */ +"Forwarding server: %@\nError: %@" = "Yönlendirme sunucusu: %1$@\nHata: %2$@"; + /* No comment provided by engineer. */ "Found desktop" = "Bilgisayar bulundu"; @@ -2460,6 +2490,9 @@ /* No comment provided by engineer. */ "Message delivery receipts!" = "Mesaj alındı bilgisi!"; +/* item status text */ +"Message delivery warning" = "Mesaj iletimi uyarısı"; + /* No comment provided by engineer. */ "Message draft" = "Mesaj taslağı"; @@ -2475,6 +2508,12 @@ /* notification */ "message received" = "mesaj alındı"; +/* No comment provided by engineer. */ +"Message routing fallback" = "Mesaj yönlendirme yedeklemesi"; + +/* No comment provided by engineer. */ +"Message routing mode" = "Mesaj yönlendirme modu"; + /* No comment provided by engineer. */ "Message source remains private." = "Mesaj kaynağı gizli kalır."; @@ -2568,9 +2607,6 @@ /* item status description */ "Most likely this connection is deleted." = "Büyük ihtimalle bu bağlantı silinmiş."; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "Büyük ihtimalle bu kişi seninle bağlantını sildi."; - /* No comment provided by engineer. */ "Multiple chat profiles" = "Çoklu sohbet profili"; @@ -2589,6 +2625,9 @@ /* No comment provided by engineer. */ "Network connection" = "Ağ bağlantısı"; +/* snd error text */ +"Network issues - message expired after many attempts to send it." = "Ağ sorunları - birçok gönderme denemesinden sonra mesajın süresi doldu."; + /* No comment provided by engineer. */ "Network management" = "Ağ yönetimi"; @@ -2951,9 +2990,18 @@ /* No comment provided by engineer. */ "Private filenames" = "Gizli dosya adları"; +/* No comment provided by engineer. */ +"Private message routing" = "Gizli mesaj yönlendirme"; + +/* No comment provided by engineer. */ +"Private message routing 🚀" = "Gizli mesaj yönlendirme 🚀"; + /* name of notes to self */ "Private notes" = "Gizli notlar"; +/* No comment provided by engineer. */ +"Private routing" = "Gizli yönlendirme"; + /* No comment provided by engineer. */ "Profile and server connections" = "Profil ve sunucu bağlantıları"; @@ -2988,7 +3036,7 @@ "Prohibit messages reactions." = "Mesajlarda tepkileri yasakla."; /* No comment provided by engineer. */ -"Prohibit sending direct messages to members." = "Geri dönülmez mesaj silme işlemini yasakla."; +"Prohibit sending direct messages to members." = "Üyelere doğrudan mesaj göndermeyi yasakla."; /* No comment provided by engineer. */ "Prohibit sending disappearing messages." = "Kaybolan mesajların gönderimini yasakla."; @@ -3005,9 +3053,15 @@ /* No comment provided by engineer. */ "Protect app screen" = "Uygulama ekranını koru"; +/* No comment provided by engineer. */ +"Protect IP address" = "IP adresini koru"; + /* No comment provided by engineer. */ "Protect your chat profiles with a password!" = "Bir parolayla birlikte sohbet profillerini koru!"; +/* No comment provided by engineer. */ +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "IP adresinizi kişileriniz tarafından seçilen mesajlaşma yönlendiricilerinden koruyun.\n*Ağ ve sunucular* ayarlarında etkinleştirin."; + /* No comment provided by engineer. */ "Protocol timeout" = "Protokol zaman aşımı"; @@ -3077,9 +3131,6 @@ /* No comment provided by engineer. */ "Receiving address will be changed to a different server. Address change will complete after sender comes online." = "Alıcı adresi farklı bir sunucuya değiştirilecektir. Gönderici çevrimiçi olduktan sonra adres değişikliği tamamlanacaktır."; -/* No comment provided by engineer. */ -"Receiving concurrency" = "Eşzamanlılık alınıyor"; - /* No comment provided by engineer. */ "Receiving file will be stopped." = "Dosya alımı durdurulacaktır."; @@ -3123,10 +3174,10 @@ "rejected call" = "geri çevrilmiş çağrı"; /* No comment provided by engineer. */ -"Relay server is only used if necessary. Another party can observe your IP address." = "Aktarma sunucusu yalnızca gerekli olduğunda kullanılır. Başka bir taraf IP adresinizi gözlemleyebilir."; +"Relay server is only used if necessary. Another party can observe your IP address." = "Yönlendirici sunucusu yalnızca gerekli olduğunda kullanılır. Başka bir taraf IP adresinizi gözlemleyebilir."; /* No comment provided by engineer. */ -"Relay server protects your IP address, but it can observe the duration of the call." = "Aktarıcı sunucu IP adresinizi korur, ancak aramanın süresini gözlemleyebilir."; +"Relay server protects your IP address, but it can observe the duration of the call." = "Yönlendirici sunucu IP adresinizi korur, ancak aramanın süresini gözlemleyebilir."; /* No comment provided by engineer. */ "Remove" = "Sil"; @@ -3236,6 +3287,9 @@ /* No comment provided by engineer. */ "Run chat" = "Sohbeti çalıştır"; +/* No comment provided by engineer. */ +"Safely receive files" = "Dosyaları güvenle alın"; + /* No comment provided by engineer. */ "Safer groups" = "Daha güvenli gruplar"; @@ -3392,6 +3446,12 @@ /* No comment provided by engineer. */ "Send live message" = "Canlı mesaj gönder"; +/* No comment provided by engineer. */ +"Send messages directly when IP address is protected and your or destination server does not support private routing." = "IP adresi korumalı olduğunda ve sizin veya hedef sunucunun özel yönlendirmeyi desteklemediği durumlarda mesajları doğrudan gönderin."; + +/* No comment provided by engineer. */ +"Send messages directly when your or destination server does not support private routing." = "Sizin veya hedef sunucunun özel yönlendirmeyi desteklemediği durumlarda mesajları doğrudan gönderin."; + /* No comment provided by engineer. */ "Send notifications" = "Bildirimler gönder"; @@ -3455,6 +3515,9 @@ /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "Gönderilen mesajlar ayarlanan süreden sonra silinecektir."; +/* srv error text. */ +"Server address is incompatible with network settings." = "Sunucu adresi ağ ayarlarıyla uyumlu değil."; + /* server test error */ "Server requires authorization to create queues, check password" = "Sunucunun sıra oluşturması için yetki gereklidir, şifreyi kontrol edin"; @@ -3464,6 +3527,9 @@ /* No comment provided by engineer. */ "Server test failed!" = "Sunucu testinde hata oluştu!"; +/* srv error text */ +"Server version is incompatible with network settings." = "Sunucu sürümü ağ ayarlarıyla uyumlu değil."; + /* No comment provided by engineer. */ "Servers" = "Sunucular"; @@ -3530,6 +3596,9 @@ /* No comment provided by engineer. */ "Share with contacts" = "Kişilerle paylaş"; +/* No comment provided by engineer. */ +"Show → on messages sent via private routing." = "Gizli yönlendirme yoluyla gönderilen mesajlarda → işaretini göster."; + /* No comment provided by engineer. */ "Show calls in phone history" = "Telefon geçmişinde aramaları göster"; @@ -3539,6 +3608,9 @@ /* No comment provided by engineer. */ "Show last messages" = "Son mesajları göster"; +/* No comment provided by engineer. */ +"Show message status" = "Mesaj durumunu göster"; + /* No comment provided by engineer. */ "Show preview" = "Ön gösterimi göser"; @@ -3746,6 +3818,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Uygulama, mesaj veya iletişim isteği aldığınızda sizi bilgilendirebilir - etkinleştirmek için lütfen ayarları açın."; +/* No comment provided by engineer. */ +"The app will ask to confirm downloads from unknown file servers (except .onion)." = "Uygulama bilinmeyen dosya sunucularından indirmeleri onaylamanızı isteyecektir (.onion hariç)."; + /* No comment provided by engineer. */ "The attempt to change database passphrase was not completed." = "Veritabanı parolasını değiştirme girişimi tamamlanmadı."; @@ -3866,6 +3941,9 @@ /* 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." = "Bilgilerinizi korumak için SimpleX Lock özelliğini açın.\nBu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenecektir."; +/* No comment provided by engineer. */ +"To protect your IP address, private routing uses your SMP servers to deliver messages." = "IP adresinizi korumak için,gizli yönlendirme mesajları iletmek için SMP sunucularınızı kullanır."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Sesli mesaj kaydetmek için lütfen Mikrofon kullanım izni verin."; @@ -3920,9 +3998,6 @@ /* rcv group event chat item */ "unblocked %@" = "engeli kaldırıldı %@"; -/* item status description */ -"Unexpected error: %@" = "Beklenmeyen hata: %@"; - /* No comment provided by engineer. */ "Unexpected migration state" = "Beklenmeyen geçiş durumu"; @@ -3953,6 +4028,12 @@ /* No comment provided by engineer. */ "Unknown error" = "Bilinmeyen hata"; +/* No comment provided by engineer. */ +"unknown relays" = "bilinmeyen yönlendiriciler"; + +/* No comment provided by engineer. */ +"Unknown servers!" = "Bilinmeyen sunucular!"; + /* No comment provided by engineer. */ "unknown status" = "bilinmeyen durum"; @@ -3977,6 +4058,9 @@ /* No comment provided by engineer. */ "Unmute" = "Susturmayı kaldır"; +/* No comment provided by engineer. */ +"unprotected" = "korumasız"; + /* No comment provided by engineer. */ "Unread" = "Okunmamış"; @@ -4046,6 +4130,12 @@ /* No comment provided by engineer. */ "Use only local notifications?" = "Sadece yerel bildirimler kullanılsın mı?"; +/* No comment provided by engineer. */ +"Use private routing with unknown servers when IP address is not protected." = "IP adresi korunmadığında bilinmeyen sunucularla gizli yönlendirme kullan."; + +/* No comment provided by engineer. */ +"Use private routing with unknown servers." = "Bilinmeyen sunucularla gizli yönlendirme kullan."; + /* No comment provided by engineer. */ "Use server" = "Sunucu kullan"; @@ -4199,6 +4289,9 @@ /* No comment provided by engineer. */ "When connecting audio and video calls." = "Sesli ve görüntülü aramalara bağlanırken."; +/* No comment provided by engineer. */ +"when IP hidden" = "IP gizliyken"; + /* No comment provided by engineer. */ "When people request to connect, you can accept or reject it." = "İnsanlar bağlantı talebinde bulunduğunda, kabul edebilir veya reddedebilirsiniz."; @@ -4223,9 +4316,18 @@ /* No comment provided by engineer. */ "With reduced battery usage." = "Azaltılmış pil kullanımı ile birlikte."; +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to file servers." = "Tor veya VPN olmadan, IP adresiniz dosya sunucularına görülebilir."; + +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Tor veya VPN olmadan, IP adresiniz bu XFTP aktarıcıları tarafından görülebilir: %@."; + /* No comment provided by engineer. */ "Wrong database passphrase" = "Yanlış veritabanı parolası"; +/* snd error text */ +"Wrong key or unknown connection - most likely this connection is deleted." = "Yanlış anahtar veya bilinmeyen bağlantı - büyük olasılıkla bu bağlantı silinmiştir."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "Yanlış parola!"; diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings index d52efd2b65..7cb4270d5b 100644 --- a/apps/ios/uk.lproj/Localizable.strings +++ b/apps/ios/uk.lproj/Localizable.strings @@ -389,6 +389,9 @@ /* member role */ "admin" = "адмін"; +/* feature role */ +"admins" = "адміністратори"; + /* No comment provided by engineer. */ "Admins can block a member for all." = "Адміністратори можуть заблокувати користувача для всіх."; @@ -416,6 +419,9 @@ /* No comment provided by engineer. */ "All group members will remain connected." = "Всі учасники групи залишаться на зв'язку."; +/* feature role */ +"all members" = "всі учасники"; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "Усі повідомлення будуть видалені - цю дію не можна скасувати!"; @@ -443,6 +449,9 @@ /* No comment provided by engineer. */ "Allow disappearing messages only if your contact allows it to you." = "Дозволяйте зникати повідомленням, тільки якщо контакт дозволяє вам це робити."; +/* No comment provided by engineer. */ +"Allow downgrade" = "Дозволити пониження версії"; + /* No comment provided by engineer. */ "Allow irreversible message deletion only if your contact allows it to you. (24 hours)" = "Дозволяйте безповоротне видалення повідомлень, тільки якщо контакт дозволяє вам це зробити. (24 години)"; @@ -464,6 +473,9 @@ /* No comment provided by engineer. */ "Allow to send files and media." = "Дозволяє надсилати файли та медіа."; +/* No comment provided by engineer. */ +"Allow to send SimpleX links." = "Дозволити надсилати посилання SimpleX."; + /* No comment provided by engineer. */ "Allow to send voice messages." = "Дозволити надсилати голосові повідомлення."; @@ -500,6 +512,9 @@ /* pref value */ "always" = "завжди"; +/* No comment provided by engineer. */ +"Always use private routing." = "Завжди використовуйте приватну маршрутизацію."; + /* No comment provided by engineer. */ "Always use relay" = "Завжди використовуйте реле"; @@ -707,6 +722,12 @@ /* No comment provided by engineer. */ "Cannot receive file" = "Не вдається отримати файл"; +/* snd error text */ +"Capacity exceeded - recipient did not receive previously sent messages." = "Перевищено ліміт - одержувач не отримав раніше надіслані повідомлення."; + +/* No comment provided by engineer. */ +"Cellular" = "Стільниковий"; + /* No comment provided by engineer. */ "Change" = "Зміна"; @@ -840,6 +861,9 @@ /* No comment provided by engineer. */ "Confirm database upgrades" = "Підтвердити оновлення бази даних"; +/* No comment provided by engineer. */ +"Confirm files from unknown servers." = "Підтвердити файли з невідомих серверів."; + /* No comment provided by engineer. */ "Confirm network settings" = "Підтвердьте налаштування мережі"; @@ -1308,6 +1332,9 @@ /* No comment provided by engineer. */ "Desktop devices" = "Настільні пристрої"; +/* snd error text */ +"Destination server error: %@" = "Помилка сервера призначення: %@"; + /* No comment provided by engineer. */ "Develop" = "Розробник"; @@ -1386,6 +1413,12 @@ /* No comment provided by engineer. */ "Do not send history to new members." = "Не надсилайте історію новим користувачам."; +/* No comment provided by engineer. */ +"Do NOT send messages directly, even if your or destination server does not support private routing." = "НЕ надсилайте повідомлення напряму, навіть якщо ваш сервер або сервер призначення не підтримує приватну маршрутизацію."; + +/* No comment provided by engineer. */ +"Do NOT use private routing." = "НЕ використовуйте приватну маршрутизацію."; + /* No comment provided by engineer. */ "Do NOT use SimpleX for emergency calls." = "НЕ використовуйте SimpleX для екстрених викликів."; @@ -1401,6 +1434,9 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "Пониження та відкритий чат"; +/* chat item action */ +"Download" = "Завантажити"; + /* No comment provided by engineer. */ "Download failed" = "Не вдалося завантажити"; @@ -1476,6 +1512,9 @@ /* enabled status */ "enabled" = "увімкнено"; +/* No comment provided by engineer. */ +"Enabled for" = "Увімкнено для"; + /* enabled status */ "enabled for contact" = "увімкнено для контакту"; @@ -1761,7 +1800,7 @@ /* No comment provided by engineer. */ "Error: " = "Помилка: "; -/* No comment provided by engineer. */ +/* snd error text */ "Error: %@" = "Помилка: %@"; /* No comment provided by engineer. */ @@ -1821,6 +1860,9 @@ /* No comment provided by engineer. */ "File: %@" = "Файл: %@"; +/* No comment provided by engineer. */ +"Files" = "Файли"; + /* No comment provided by engineer. */ "Files & media" = "Файли та медіа"; @@ -1830,6 +1872,9 @@ /* No comment provided by engineer. */ "Files and media are prohibited in this group." = "Файли та медіа в цій групі заборонені."; +/* No comment provided by engineer. */ +"Files and media not allowed" = "Файли та медіафайли заборонені"; + /* No comment provided by engineer. */ "Files and media prohibited!" = "Файли та медіа заборонені!"; @@ -1869,6 +1914,27 @@ /* No comment provided by engineer. */ "For console" = "Для консолі"; +/* chat item action */ +"Forward" = "Пересилання"; + +/* No comment provided by engineer. */ +"Forward and save messages" = "Пересилання та збереження повідомлень"; + +/* No comment provided by engineer. */ +"forwarded" = "переслано"; + +/* No comment provided by engineer. */ +"Forwarded" = "Переслано"; + +/* No comment provided by engineer. */ +"Forwarded from" = "Переслано з"; + +/* snd error text */ +"Forwarding server: %@\nDestination server error: %@" = "Сервер переадресації: %1$@\nПомилка сервера призначення: %2$@"; + +/* snd error text */ +"Forwarding server: %@\nError: %@" = "Сервер переадресації: %1$@\nПомилка: %2$@"; + /* No comment provided by engineer. */ "Found desktop" = "Знайдено робочий стіл"; @@ -1947,6 +2013,9 @@ /* No comment provided by engineer. */ "Group members can send files and media." = "Учасники групи можуть надсилати файли та медіа."; +/* No comment provided by engineer. */ +"Group members can send SimpleX links." = "Учасники групи можуть надсилати посилання SimpleX."; + /* No comment provided by engineer. */ "Group members can send voice messages." = "Учасники групи можуть надсилати голосові повідомлення."; @@ -2088,6 +2157,9 @@ /* No comment provided by engineer. */ "In reply to" = "У відповідь на"; +/* No comment provided by engineer. */ +"In-call sounds" = "Звуки вхідного дзвінка"; + /* No comment provided by engineer. */ "Incognito" = "Інкогніто"; @@ -2418,6 +2490,9 @@ /* No comment provided by engineer. */ "Message delivery receipts!" = "Підтвердження доставки повідомлення!"; +/* item status text */ +"Message delivery warning" = "Попередження про доставку повідомлення"; + /* No comment provided by engineer. */ "Message draft" = "Чернетка повідомлення"; @@ -2433,6 +2508,15 @@ /* notification */ "message received" = "повідомлення отримано"; +/* No comment provided by engineer. */ +"Message routing fallback" = "Запасний варіант маршрутизації повідомлень"; + +/* No comment provided by engineer. */ +"Message routing mode" = "Режим маршрутизації повідомлень"; + +/* No comment provided by engineer. */ +"Message source remains private." = "Джерело повідомлення залишається приватним."; + /* No comment provided by engineer. */ "Message text" = "Текст повідомлення"; @@ -2517,12 +2601,12 @@ /* No comment provided by engineer. */ "More improvements are coming soon!" = "Незабаром буде ще більше покращень!"; +/* No comment provided by engineer. */ +"More reliable network connection." = "Більш надійне з'єднання з мережею."; + /* item status description */ "Most likely this connection is deleted." = "Швидше за все, це з'єднання видалено."; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "Швидше за все, цей контакт видалив зв'язок з вами."; - /* No comment provided by engineer. */ "Multiple chat profiles" = "Кілька профілів чату"; @@ -2538,6 +2622,15 @@ /* No comment provided by engineer. */ "Network & servers" = "Мережа та сервери"; +/* No comment provided by engineer. */ +"Network connection" = "Підключення до мережі"; + +/* snd error text */ +"Network issues - message expired after many attempts to send it." = "Проблеми з мережею - термін дії повідомлення закінчився після багатьох спроб надіслати його."; + +/* No comment provided by engineer. */ +"Network management" = "Керування мережею"; + /* No comment provided by engineer. */ "Network settings" = "Налаштування мережі"; @@ -2616,6 +2709,9 @@ /* No comment provided by engineer. */ "No history" = "Немає історії"; +/* No comment provided by engineer. */ +"No network connection" = "Немає підключення до мережі"; + /* No comment provided by engineer. */ "No permission to record voice message" = "Немає дозволу на запис голосового повідомлення"; @@ -2762,9 +2858,15 @@ /* No comment provided by engineer. */ "Or show this code" = "Або покажіть цей код"; +/* No comment provided by engineer. */ +"Other" = "Інше"; + /* member role */ "owner" = "власник"; +/* feature role */ +"owners" = "власники"; + /* No comment provided by engineer. */ "Passcode" = "Пароль"; @@ -2888,15 +2990,27 @@ /* No comment provided by engineer. */ "Private filenames" = "Приватні імена файлів"; +/* No comment provided by engineer. */ +"Private message routing" = "Маршрутизація приватних повідомлень"; + +/* No comment provided by engineer. */ +"Private message routing 🚀" = "Маршрутизація приватних повідомлень 🚀"; + /* name of notes to self */ "Private notes" = "Приватні нотатки"; +/* No comment provided by engineer. */ +"Private routing" = "Приватна маршрутизація"; + /* No comment provided by engineer. */ "Profile and server connections" = "З'єднання профілю та сервера"; /* No comment provided by engineer. */ "Profile image" = "Зображення профілю"; +/* No comment provided by engineer. */ +"Profile images" = "Зображення профілю"; + /* No comment provided by engineer. */ "Profile name" = "Назва профілю"; @@ -2930,15 +3044,24 @@ /* No comment provided by engineer. */ "Prohibit sending files and media." = "Заборонити надсилання файлів і медіа."; +/* No comment provided by engineer. */ +"Prohibit sending SimpleX links." = "Заборонити надсилання посилань SimpleX."; + /* No comment provided by engineer. */ "Prohibit sending voice messages." = "Заборонити надсилання голосових повідомлень."; /* No comment provided by engineer. */ "Protect app screen" = "Захистіть екран програми"; +/* No comment provided by engineer. */ +"Protect IP address" = "Захист IP-адреси"; + /* No comment provided by engineer. */ "Protect your chat profiles with a password!" = "Захистіть свої профілі чату паролем!"; +/* No comment provided by engineer. */ +"Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings." = "Захистіть свою IP-адресу від ретрансляторів повідомлень, обраних вашими контактами.\nУвімкніть у налаштуваннях *Мережа та сервери*."; + /* No comment provided by engineer. */ "Protocol timeout" = "Тайм-аут протоколу"; @@ -3017,6 +3140,9 @@ /* 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)." = "Нещодавня історія та покращення [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)."; +/* No comment provided by engineer. */ +"Recipient(s) can't see who this message is from." = "Одержувач(и) не бачить, від кого це повідомлення."; + /* No comment provided by engineer. */ "Recipients see updates as you type them." = "Одержувачі бачать оновлення, коли ви їх вводите."; @@ -3161,6 +3287,9 @@ /* No comment provided by engineer. */ "Run chat" = "Запустити чат"; +/* No comment provided by engineer. */ +"Safely receive files" = "Безпечне отримання файлів"; + /* No comment provided by engineer. */ "Safer groups" = "Безпечніші групи"; @@ -3212,6 +3341,18 @@ /* No comment provided by engineer. */ "Save welcome message?" = "Зберегти вітальне повідомлення?"; +/* No comment provided by engineer. */ +"saved" = "збережено"; + +/* No comment provided by engineer. */ +"Saved" = "Збережено"; + +/* No comment provided by engineer. */ +"Saved from" = "Збережено з"; + +/* No comment provided by engineer. */ +"saved from %@" = "збережено з %@"; + /* message info title */ "Saved message" = "Збережене повідомлення"; @@ -3305,6 +3446,12 @@ /* No comment provided by engineer. */ "Send live message" = "Надіслати живе повідомлення"; +/* No comment provided by engineer. */ +"Send messages directly when IP address is protected and your or destination server does not support private routing." = "Надсилайте повідомлення напряму, якщо IP-адреса захищена, а ваш сервер або сервер призначення не підтримує приватну маршрутизацію."; + +/* No comment provided by engineer. */ +"Send messages directly when your or destination server does not support private routing." = "Надсилайте повідомлення напряму, якщо ваш сервер або сервер призначення не підтримує приватну маршрутизацію."; + /* No comment provided by engineer. */ "Send notifications" = "Надсилати сповіщення"; @@ -3368,6 +3515,9 @@ /* No comment provided by engineer. */ "Sent messages will be deleted after set time." = "Надіслані повідомлення будуть видалені через встановлений час."; +/* srv error text. */ +"Server address is incompatible with network settings." = "Адреса сервера несумісна з налаштуваннями мережі."; + /* server test error */ "Server requires authorization to create queues, check password" = "Сервер вимагає авторизації для створення черг, перевірте пароль"; @@ -3377,6 +3527,9 @@ /* No comment provided by engineer. */ "Server test failed!" = "Тест сервера завершився невдало!"; +/* srv error text */ +"Server version is incompatible with network settings." = "Серверна версія несумісна з мережевими налаштуваннями."; + /* No comment provided by engineer. */ "Servers" = "Сервери"; @@ -3419,6 +3572,9 @@ /* No comment provided by engineer. */ "Settings" = "Налаштування"; +/* No comment provided by engineer. */ +"Shape profile images" = "Сформуйте зображення профілю"; + /* chat item action */ "Share" = "Поділіться"; @@ -3440,6 +3596,9 @@ /* No comment provided by engineer. */ "Share with contacts" = "Поділіться з контактами"; +/* No comment provided by engineer. */ +"Show → on messages sent via private routing." = "Показувати → у повідомленнях, надісланих через приватну маршрутизацію."; + /* No comment provided by engineer. */ "Show calls in phone history" = "Показувати дзвінки в історії дзвінків"; @@ -3449,6 +3608,9 @@ /* No comment provided by engineer. */ "Show last messages" = "Показати останні повідомлення"; +/* No comment provided by engineer. */ +"Show message status" = "Показати статус повідомлення"; + /* No comment provided by engineer. */ "Show preview" = "Показати попередній перегляд"; @@ -3479,6 +3641,12 @@ /* chat feature */ "SimpleX links" = "Посилання SimpleX"; +/* No comment provided by engineer. */ +"SimpleX links are prohibited in this group." = "У цій групі заборонені посилання на SimpleX."; + +/* No comment provided by engineer. */ +"SimpleX links not allowed" = "Посилання SimpleX заборонені"; + /* No comment provided by engineer. */ "SimpleX Lock" = "SimpleX Lock"; @@ -3515,6 +3683,9 @@ /* notification title */ "Somebody" = "Хтось"; +/* No comment provided by engineer. */ +"Square, circle, or anything in between." = "Квадрат, коло або щось середнє між ними."; + /* chat item text */ "standard end-to-end encryption" = "стандартне наскрізне шифрування"; @@ -3647,6 +3818,9 @@ /* No comment provided by engineer. */ "The app can notify you when you receive messages or contact requests - please open settings to enable." = "Додаток може сповіщати вас, коли ви отримуєте повідомлення або запити на контакт - будь ласка, відкрийте налаштування, щоб увімкнути цю функцію."; +/* No comment provided by engineer. */ +"The app will ask to confirm downloads from unknown file servers (except .onion)." = "Програма попросить підтвердити завантаження з невідомих файлових серверів (крім .onion)."; + /* No comment provided by engineer. */ "The attempt to change database passphrase was not completed." = "Спроба змінити пароль до бази даних не була завершена."; @@ -3767,6 +3941,9 @@ /* No comment provided by engineer. */ "To protect your information, turn on SimpleX Lock.\nYou will be prompted to complete authentication before this feature is enabled." = "Щоб захистити вашу інформацію, увімкніть SimpleX Lock.\nПеред увімкненням цієї функції вам буде запропоновано пройти автентифікацію."; +/* No comment provided by engineer. */ +"To protect your IP address, private routing uses your SMP servers to deliver messages." = "Щоб захистити вашу IP-адресу, приватна маршрутизація використовує ваші SMP-сервери для доставки повідомлень."; + /* No comment provided by engineer. */ "To record voice message please grant permission to use Microphone." = "Щоб записати голосове повідомлення, будь ласка, надайте дозвіл на використання мікрофону."; @@ -3821,9 +3998,6 @@ /* rcv group event chat item */ "unblocked %@" = "розблоковано %@"; -/* item status description */ -"Unexpected error: %@" = "Неочікувана помилка: %@"; - /* No comment provided by engineer. */ "Unexpected migration state" = "Неочікуваний стан міграції"; @@ -3854,6 +4028,12 @@ /* No comment provided by engineer. */ "Unknown error" = "Невідома помилка"; +/* No comment provided by engineer. */ +"unknown relays" = "невідомі реле"; + +/* No comment provided by engineer. */ +"Unknown servers!" = "Невідомі сервери!"; + /* No comment provided by engineer. */ "unknown status" = "невідомий статус"; @@ -3878,6 +4058,9 @@ /* No comment provided by engineer. */ "Unmute" = "Увімкнути звук"; +/* No comment provided by engineer. */ +"unprotected" = "незахищені"; + /* No comment provided by engineer. */ "Unread" = "Непрочитане"; @@ -3947,6 +4130,12 @@ /* No comment provided by engineer. */ "Use only local notifications?" = "Використовувати лише локальні сповіщення?"; +/* No comment provided by engineer. */ +"Use private routing with unknown servers when IP address is not protected." = "Використовуйте приватну маршрутизацію з невідомими серверами, якщо IP-адреса не захищена."; + +/* No comment provided by engineer. */ +"Use private routing with unknown servers." = "Використовуйте приватну маршрутизацію з невідомими серверами."; + /* No comment provided by engineer. */ "Use server" = "Використовувати сервер"; @@ -4043,6 +4232,9 @@ /* No comment provided by engineer. */ "Voice messages are prohibited in this group." = "Голосові повідомлення в цій групі заборонені."; +/* No comment provided by engineer. */ +"Voice messages not allowed" = "Голосові повідомлення заборонені"; + /* No comment provided by engineer. */ "Voice messages prohibited!" = "Голосові повідомлення заборонені!"; @@ -4094,12 +4286,27 @@ /* No comment provided by engineer. */ "When available" = "За наявності"; +/* No comment provided by engineer. */ +"When connecting audio and video calls." = "При підключенні аудіо та відеодзвінків."; + +/* No comment provided by engineer. */ +"when IP hidden" = "коли IP приховано"; + /* No comment provided by engineer. */ "When people request to connect, you can accept or reject it." = "Коли люди звертаються із запитом на підключення, ви можете прийняти або відхилити його."; /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "Коли ви ділитеся з кимось своїм профілем інкогніто, цей профіль буде використовуватися для груп, до яких вас запрошують."; +/* No comment provided by engineer. */ +"WiFi" = "WiFi"; + +/* No comment provided by engineer. */ +"Will be enabled in direct chats!" = "Буде ввімкнено в прямих чатах!"; + +/* No comment provided by engineer. */ +"Wired ethernet" = "Дротова мережа Ethernet"; + /* No comment provided by engineer. */ "With encrypted files and media." = "З зашифрованими файлами та медіа."; @@ -4109,9 +4316,18 @@ /* No comment provided by engineer. */ "With reduced battery usage." = "З меншим споживанням заряду акумулятора."; +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to file servers." = "Без Tor або VPN ваша IP-адреса буде видимою для файлових серверів."; + +/* No comment provided by engineer. */ +"Without Tor or VPN, your IP address will be visible to these XFTP relays: %@." = "Без Tor або VPN ваша IP-адреса буде видимою для цих XFTP-ретрансляторів: %@."; + /* No comment provided by engineer. */ "Wrong database passphrase" = "Неправильний пароль до бази даних"; +/* snd error text */ +"Wrong key or unknown connection - most likely this connection is deleted." = "Неправильний ключ або невідоме з'єднання - швидше за все, це з'єднання видалено."; + /* No comment provided by engineer. */ "Wrong passphrase!" = "Неправильний пароль!"; @@ -4121,6 +4337,9 @@ /* pref value */ "yes" = "так"; +/* No comment provided by engineer. */ +"you" = "ти"; + /* No comment provided by engineer. */ "You" = "Ти"; diff --git a/apps/ios/zh-Hans.lproj/Localizable.strings b/apps/ios/zh-Hans.lproj/Localizable.strings index 5af77e4e51..74d9970b93 100644 --- a/apps/ios/zh-Hans.lproj/Localizable.strings +++ b/apps/ios/zh-Hans.lproj/Localizable.strings @@ -353,6 +353,12 @@ /* member role */ "admin" = "管理员"; +/* feature role */ +"admins" = "管理员"; + +/* No comment provided by engineer. */ +"Admins can block a member for all." = "管理员可以为所有人封禁一名成员。"; + /* No comment provided by engineer. */ "Admins can create the links to join groups." = "管理员可以创建链接以加入群组。"; @@ -377,6 +383,9 @@ /* No comment provided by engineer. */ "All group members will remain connected." = "所有群组成员将保持连接。"; +/* feature role */ +"all members" = "所有成员"; + /* No comment provided by engineer. */ "All messages will be deleted - this cannot be undone!" = "所有消息都将被删除 - 这无法被撤销!"; @@ -389,6 +398,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" = "允许"; @@ -419,6 +431,9 @@ /* No comment provided by engineer. */ "Allow to send files and media." = "允许发送文件和媒体。"; +/* No comment provided by engineer. */ +"Allow to send SimpleX links." = "允许发送 SimpleX 链接。"; + /* No comment provided by engineer. */ "Allow to send voice messages." = "允许发送语音消息。"; @@ -467,6 +482,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)." = "应用程序为新的本地文件(视频除外)加密。"; @@ -488,6 +506,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" = "附件"; @@ -635,6 +662,9 @@ /* No comment provided by engineer. */ "Cancel" = "取消"; +/* No comment provided by engineer. */ +"Cancel migration" = "取消迁移"; + /* feature offered item */ "cancelled %@" = "已取消 %@"; @@ -644,6 +674,9 @@ /* No comment provided by engineer. */ "Cannot receive file" = "无法接收文件"; +/* No comment provided by engineer. */ +"Cellular" = "移动网络"; + /* No comment provided by engineer. */ "Change" = "更改"; @@ -714,6 +747,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" = "聊天偏好设置"; @@ -771,6 +807,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…" = "确认新密码……"; @@ -780,6 +819,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" = "连接"; @@ -957,6 +1002,9 @@ /* No comment provided by engineer. */ "Created on %@" = "创建于 %@"; +/* No comment provided by engineer. */ +"Creating archive link" = "正在创建存档链接"; + /* No comment provided by engineer. */ "Creating link…" = "创建链接中…"; @@ -1098,6 +1146,9 @@ /* No comment provided by engineer. */ "Delete database" = "删除数据库"; +/* No comment provided by engineer. */ +"Delete database from this device" = "从这部设备上删除数据库"; + /* server test step */ "Delete file" = "删除文件"; @@ -1287,9 +1338,21 @@ /* No comment provided by engineer. */ "Downgrade and open chat" = "降级并打开聊天"; +/* chat item action */ +"Download" = "下载"; + +/* 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!" = "重复的显示名!"; @@ -1323,6 +1386,9 @@ /* No comment provided by engineer. */ "Enable for all" = "全部启用"; +/* No comment provided by engineer. */ +"Enable in direct chats (BETA)!" = "在私聊中开启(公测)!"; + /* No comment provided by engineer. */ "Enable instant notifications?" = "启用即时通知?"; @@ -1350,6 +1416,9 @@ /* enabled status */ "enabled" = "已启用"; +/* No comment provided by engineer. */ +"Enabled for" = "启用对象"; + /* enabled status */ "enabled for contact" = "已为联系人启用"; @@ -1431,6 +1500,9 @@ /* No comment provided by engineer. */ "Enter Passcode" = "输入密码"; +/* No comment provided by engineer. */ +"Enter passphrase" = "输入密码短语"; + /* No comment provided by engineer. */ "Enter passphrase…" = "输入密码……"; @@ -1521,6 +1593,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!" = "启用送达回执出错!"; @@ -1563,6 +1638,9 @@ /* No comment provided by engineer. */ "Error saving passphrase to keychain" = "保存密码到钥匙串错误"; +/* when migrating */ +"Error saving settings" = "保存设置出错"; + /* No comment provided by engineer. */ "Error saving user password" = "保存用户密码时出错"; @@ -1603,9 +1681,15 @@ "Error updating user privacy" = "更新用户隐私时出错"; /* No comment provided by engineer. */ -"Error: " = "错误: "; +"Error uploading the archive" = "上传存档出错"; /* No comment provided by engineer. */ +"Error verifying passphrase:" = "验证密码短语出错:"; + +/* No comment provided by engineer. */ +"Error: " = "错误: "; + +/* snd error text */ "Error: %@" = "错误: %@"; /* No comment provided by engineer. */ @@ -1635,6 +1719,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…" = "导出数据库档案中…"; @@ -1671,12 +1758,21 @@ /* No comment provided by engineer. */ "Files and media are prohibited in this group." = "此群组中禁止文件和媒体。"; +/* No comment provided by engineer. */ +"Files and media not allowed" = "不允许文件和媒体"; + /* No comment provided by engineer. */ "Files and media prohibited!" = "禁止文件和媒体!"; /* 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! 🚀" = "终于我们有它们了! 🚀"; @@ -1704,6 +1800,21 @@ /* No comment provided by engineer. */ "For console" = "用于控制台"; +/* chat item action */ +"Forward" = "转发"; + +/* No comment provided by engineer. */ +"Forward and save messages" = "转发并保存消息"; + +/* No comment provided by engineer. */ +"forwarded" = "已转发"; + +/* No comment provided by engineer. */ +"Forwarded" = "已转发"; + +/* No comment provided by engineer. */ +"Forwarded from" = "转发自"; + /* No comment provided by engineer. */ "Found desktop" = "找到了桌面"; @@ -1779,6 +1890,9 @@ /* No comment provided by engineer. */ "Group members can send files and media." = "群组成员可以发送文件和媒体。"; +/* No comment provided by engineer. */ +"Group members can send SimpleX links." = "群成员可发送 SimpleX 链接。"; + /* No comment provided by engineer. */ "Group members can send voice messages." = "群组成员可以发送语音消息。"; @@ -1896,6 +2010,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" = "改进了消息传递"; @@ -1905,9 +2025,15 @@ /* 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" = "答复"; +/* No comment provided by engineer. */ +"In-call sounds" = "通话声音"; + /* No comment provided by engineer. */ "Incognito" = "隐身聊天"; @@ -1986,6 +2112,12 @@ /* No comment provided by engineer. */ "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!" = "无效名称!"; @@ -2232,18 +2364,45 @@ /* notification */ "message received" = "消息已收到"; +/* No comment provided by engineer. */ +"Message source remains private." = "消息来源保持私密。"; + /* No comment provided by engineer. */ "Message text" = "消息正文"; +/* No comment provided by engineer. */ +"Message too large" = "消息太大了"; + /* No comment provided by engineer. */ "Messages" = "消息"; /* No comment provided by engineer. */ "Messages & files" = "消息"; +/* 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:" = "迁移错误:"; @@ -2283,12 +2442,12 @@ /* No comment provided by engineer. */ "More improvements are coming soon!" = "更多改进即将推出!"; +/* No comment provided by engineer. */ +"More reliable network connection." = "更可靠的网络连接。"; + /* item status description */ "Most likely this connection is deleted." = "此连接很可能已被删除。"; -/* No comment provided by engineer. */ -"Most likely this contact has deleted the connection with you." = "很可能此联系人已经删除了与您的联系。"; - /* No comment provided by engineer. */ "Multiple chat profiles" = "多个聊天资料"; @@ -2304,6 +2463,12 @@ /* No comment provided by engineer. */ "Network & servers" = "网络和服务器"; +/* No comment provided by engineer. */ +"Network connection" = "网络连接"; + +/* No comment provided by engineer. */ +"Network management" = "网络管理"; + /* No comment provided by engineer. */ "Network settings" = "网络设置"; @@ -2382,6 +2547,9 @@ /* No comment provided by engineer. */ "No history" = "无历史记录"; +/* No comment provided by engineer. */ +"No network connection" = "无网络连接"; + /* No comment provided by engineer. */ "No permission to record voice message" = "没有录制语音消息的权限"; @@ -2510,15 +2678,27 @@ /* No comment provided by engineer. */ "Open-source protocol and code – anybody can run the servers." = "开源协议和代码——任何人都可以运行服务器。"; +/* 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. */ +"Other" = "其他"; + /* member role */ "owner" = "群主"; +/* feature role */ +"owners" = "所有者"; + /* No comment provided by engineer. */ "Passcode" = "密码"; @@ -2561,6 +2741,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 次数"; @@ -2579,6 +2762,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 group admin." = "请联系群组管理员。"; @@ -2639,6 +2825,9 @@ /* No comment provided by engineer. */ "Profile image" = "资料图片"; +/* No comment provided by engineer. */ +"Profile images" = "个人资料图"; + /* No comment provided by engineer. */ "Profile name:" = "显示名:"; @@ -2687,6 +2876,12 @@ /* No comment provided by engineer. */ "Push notifications" = "推送通知"; +/* chat item text */ +"quantum resistant e2e encryption" = "抗量子端到端加密"; + +/* No comment provided by engineer. */ +"Quantum resistant encryption" = "抗量子加密"; + /* No comment provided by engineer. */ "Rate the app" = "评价此应用程序"; @@ -2744,6 +2939,9 @@ /* No comment provided by engineer. */ "Receiving via" = "接收通过"; +/* No comment provided by engineer. */ +"Recipient(s) can't see who this message is from." = "收件人看不到这条消息来自何人。"; + /* No comment provided by engineer. */ "Recipients see updates as you type them." = "对方会在您键入时看到更新。"; @@ -2819,9 +3017,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" = "回复"; @@ -2879,6 +3086,9 @@ /* No comment provided by engineer. */ "Run chat" = "运行聊天程序"; +/* No comment provided by engineer. */ +"Safer groups" = "更安全的群组"; + /* chat item action */ "Save" = "保存"; @@ -2927,6 +3137,15 @@ /* No comment provided by engineer. */ "Save welcome message?" = "保存欢迎信息?"; +/* No comment provided by engineer. */ +"saved" = "已保存"; + +/* No comment provided by engineer. */ +"Saved" = "已保存"; + +/* No comment provided by engineer. */ +"Saved from" = "保存自"; + /* message info title */ "Saved message" = "已保存的消息"; @@ -3119,6 +3338,9 @@ /* No comment provided by engineer. */ "Set passcode" = "设置密码"; +/* No comment provided by engineer. */ +"Set passphrase" = "设置密码短语"; + /* No comment provided by engineer. */ "Set passphrase to export" = "设置密码来导出"; @@ -3131,6 +3353,9 @@ /* No comment provided by engineer. */ "Settings" = "设置"; +/* No comment provided by engineer. */ +"Shape profile images" = "改变个人资料图形状"; + /* chat item action */ "Share" = "分享"; @@ -3164,6 +3389,9 @@ /* No comment provided by engineer. */ "Show preview" = "显示预览"; +/* No comment provided by engineer. */ +"Show QR code" = "显示二维码"; + /* No comment provided by engineer. */ "Show:" = "显示:"; @@ -3188,6 +3416,12 @@ /* chat feature */ "SimpleX links" = "SimpleX 链接"; +/* No comment provided by engineer. */ +"SimpleX links are prohibited in this group." = "此群禁止 SimpleX 链接。"; + +/* No comment provided by engineer. */ +"SimpleX links not allowed" = "不允许SimpleX 链接"; + /* No comment provided by engineer. */ "SimpleX Lock" = "SimpleX 锁定"; @@ -3224,6 +3458,12 @@ /* notification title */ "Somebody" = "某人"; +/* No comment provided by engineer. */ +"Square, circle, or anything in between." = "方形、圆形、或两者之间的任意形状"; + +/* chat item text */ +"standard end-to-end encryption" = "标准端到端加密"; + /* No comment provided by engineer. */ "Start chat" = "开始聊天"; @@ -3239,6 +3479,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" = "停止聊天以启用数据库操作"; @@ -3266,6 +3509,9 @@ /* authentication reason */ "Stop SimpleX" = "停止 SimpleX"; +/* No comment provided by engineer. */ +"Stopping chat" = "正在停止聊天"; + /* No comment provided by engineer. */ "strike" = "删去"; @@ -3416,6 +3662,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." = "此聊天受端到端加密保护。"; + +/* E2EE info chat item */ +"This chat is protected by quantum resistant end-to-end encryption." = "此聊天受抗量子的端到端加密保护。"; + /* notification title */ "this contact" = "这个联系人"; @@ -3509,9 +3761,6 @@ /* No comment provided by engineer. */ "Unblock member?" = "解封成员吗?"; -/* item status description */ -"Unexpected error: %@" = "意外错误: %@"; - /* No comment provided by engineer. */ "Unexpected migration state" = "未预料的迁移状态"; @@ -3602,9 +3851,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 主机"; @@ -3632,6 +3887,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" = "用户资料"; @@ -3656,6 +3914,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" = "验证安全码"; @@ -3710,6 +3974,9 @@ /* No comment provided by engineer. */ "Voice messages are prohibited in this group." = "语音信息在该群组中被禁用。"; +/* No comment provided by engineer. */ +"Voice messages not allowed" = "不允许语音消息"; + /* No comment provided by engineer. */ "Voice messages prohibited!" = "语音消息禁止发送!"; @@ -3731,6 +3998,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!" = "警告:您可能会丢失部分数据!"; @@ -3746,18 +4016,33 @@ /* 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" = "更新内容"; /* No comment provided by engineer. */ "When available" = "当可用时"; +/* No comment provided by engineer. */ +"When connecting audio and video calls." = "连接音频和视频通话时。"; + /* No comment provided by engineer. */ "When people request to connect, you can accept or reject it." = "当人们请求连接时,您可以接受或拒绝它。"; /* No comment provided by engineer. */ "When you share an incognito profile with somebody, this profile will be used for the groups they invite you to." = "当您与某人共享隐身聊天资料时,该资料将用于他们邀请您加入的群组。"; +/* No comment provided by engineer. */ +"WiFi" = "WiFi"; + +/* No comment provided by engineer. */ +"Will be enabled in direct chats!" = "将在私聊中启用!"; + +/* No comment provided by engineer. */ +"Wired ethernet" = "有线以太网"; + /* No comment provided by engineer. */ "With encrypted files and media." = "加密的文件和媒体。"; @@ -3779,6 +4064,9 @@ /* pref value */ "yes" = "是"; +/* No comment provided by engineer. */ +"you" = "您"; + /* No comment provided by engineer. */ "You" = "您"; @@ -3824,6 +4112,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/multiplatform/android/build.gradle.kts b/apps/multiplatform/android/build.gradle.kts index 4e279d3ccb..5c2c786a21 100644 --- a/apps/multiplatform/android/build.gradle.kts +++ b/apps/multiplatform/android/build.gradle.kts @@ -88,6 +88,7 @@ android { "cs", "de", "es", + "fa", "fi", "fr", "hu", diff --git a/apps/multiplatform/android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt b/apps/multiplatform/android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt index 0152f5e8c2..64b6639a58 100644 --- a/apps/multiplatform/android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt +++ b/apps/multiplatform/android/src/main/java/chat/simplex/app/MessagesFetcherWorker.kt @@ -7,7 +7,7 @@ import chat.simplex.app.SimplexService.Companion.showPassphraseNotification import chat.simplex.common.model.ChatController import chat.simplex.common.views.helpers.DBMigrationResult import chat.simplex.common.platform.chatModel -import chat.simplex.common.platform.initChatControllerAndRunMigrations +import chat.simplex.common.platform.initChatControllerOnStart import chat.simplex.common.views.helpers.DatabaseUtils import kotlinx.coroutines.* import java.util.Date @@ -60,7 +60,7 @@ class MessagesFetcherWork( try { // 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) { - initChatControllerAndRunMigrations() + initChatControllerOnStart() } withTimeout(durationSeconds * 1000L) { val chatController = ChatController 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 83105c678a..95bba8e8a2 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 @@ -6,7 +6,6 @@ import android.content.Context import chat.simplex.common.platform.Log 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 @@ -66,6 +65,7 @@ class SimplexApp: Application(), LifecycleEventObserver { context = this initHaskell() initMultiplatform() + runMigrations() tmpDir.deleteRecursively() tmpDir.mkdir() @@ -74,7 +74,7 @@ class SimplexApp: Application(), LifecycleEventObserver { // 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() + initChatControllerOnStart() } ProcessLifecycleOwner.get().lifecycle.addObserver(this@SimplexApp) } @@ -254,7 +254,7 @@ class SimplexApp: Application(), LifecycleEventObserver { override fun androidSetNightModeIfSupported() { if (Build.VERSION.SDK_INT < 31) return - val light = if (CurrentColors.value.name == DefaultTheme.SYSTEM.name) { + val light = if (CurrentColors.value.name == DefaultTheme.SYSTEM_THEME_NAME) { null } else { CurrentColors.value.colors.isLight 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 f56bf4fe00..a5f5d84ec2 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 @@ -77,7 +77,7 @@ class SimplexService: Service() { 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) { - initChatControllerAndRunMigrations() + initChatControllerOnStart() } } } 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 f83933b2e0..69ad8defbf 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 @@ -77,7 +77,7 @@ object NtfManager { val msgNtfs = manager.activeNotifications.filter { ntf -> ntf.notification.channelId == MessageChannel } - if (msgNtfs.count() == 1) { + if (msgNtfs.size <= 1) { // Have a group notification with no children so cancel it manager.cancel(0) } diff --git a/apps/multiplatform/common/build.gradle.kts b/apps/multiplatform/common/build.gradle.kts index 1f55a7195c..ac131c9748 100644 --- a/apps/multiplatform/common/build.gradle.kts +++ b/apps/multiplatform/common/build.gradle.kts @@ -37,7 +37,7 @@ kotlin { api("org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.3") api("org.jetbrains.kotlinx:kotlinx-datetime:0.5.0") api("com.russhwolf:multiplatform-settings:1.1.1") - api("com.charleskorn.kaml:kaml:0.58.0") + api("com.charleskorn.kaml:kaml:0.59.0") api("org.jetbrains.compose.ui:ui-text:${rootProject.extra["compose.version"] as String}") implementation("org.jetbrains.compose.components:components-animatedimage:${rootProject.extra["compose.version"] as String}") //Barcode @@ -53,6 +53,9 @@ kotlin { val commonTest by getting { dependencies { implementation(kotlin("test")) + implementation(kotlin("test-junit")) + implementation(kotlin("test-common")) + implementation(kotlin("test-annotations-common")) } } val androidMain by getting { diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Extensions.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Extensions.kt index e237272eb0..eb87dd1e28 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Extensions.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/helpers/Extensions.kt @@ -17,6 +17,6 @@ val NotificationsMode.requiresIgnoringBattery lateinit var APPLICATION_ID: String -fun Uri.toURI(): URI = URI(toString()) +fun Uri.toURI(): URI = URI(toString().replace("\n", "")) fun URI.toUri(): Uri = Uri.parse(toString()) diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt index dfc8c1d4e7..ea092453ee 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Files.android.kt @@ -15,8 +15,10 @@ actual val dataDir: File = androidAppContext.dataDir actual val tmpDir: File = androidAppContext.getDir("temp", Application.MODE_PRIVATE) actual val filesDir: File = File(dataDir.absolutePath + File.separator + "files") actual val appFilesDir: File = File(filesDir.absolutePath + File.separator + "app_files") +actual val wallpapersDir: File = File(filesDir.absolutePath + File.separator + "assets" + File.separator + "wallpapers").also { it.mkdirs() } actual val coreTmpDir: File = File(filesDir.absolutePath + File.separator + "temp_files") actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "files" +actual val preferencesDir = File(dataDir.absolutePath + File.separator + "shared_prefs") actual val chatDatabaseFileName: String = "files_chat.db" actual val agentDatabaseFileName: String = "files_agent.db" diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt index e15d1f9268..91d19759ea 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/Resources.android.kt @@ -7,6 +7,8 @@ import android.content.SharedPreferences import android.content.res.Configuration import android.text.BidiFormatter import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalConfiguration import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.font.Font @@ -14,9 +16,11 @@ import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.core.graphics.drawable.toBitmap import chat.simplex.common.model.AppPreferences import com.russhwolf.settings.Settings import com.russhwolf.settings.SharedPreferencesSettings +import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.desc.desc @@ -51,3 +55,6 @@ actual fun windowWidth(): Dp = LocalConfiguration.current.screenWidthDp.dp actual fun desktopExpandWindowToWidth(width: Dp) {} actual fun isRtl(text: CharSequence): Boolean = BidiFormatter.getInstance().isRtl(text) + +actual fun ImageResource.toComposeImageBitmap(): ImageBitmap? = + getDrawable(androidAppContext)?.toBitmap()?.asImageBitmap() 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 c702264d82..d6af35432d 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 @@ -11,6 +11,7 @@ import android.media.* import android.os.Build import android.os.PowerManager import android.os.PowerManager.PROXIMITY_SCREEN_OFF_WAKE_LOCK +import android.view.View import android.view.ViewGroup import android.webkit.* import androidx.compose.desktop.ui.tooling.preview.Preview @@ -670,37 +671,43 @@ fun WebRTCView(callCommand: SnapshotStateList, onResponse: (WVAPIM Box(Modifier.fillMaxSize()) { AndroidView( factory = { AndroidViewContext -> - (staticWebView ?: WebView(androidAppContext)).apply { - layoutParams = ViewGroup.LayoutParams( - ViewGroup.LayoutParams.MATCH_PARENT, - ViewGroup.LayoutParams.MATCH_PARENT, - ) - this.webChromeClient = object: WebChromeClient() { - override fun onPermissionRequest(request: PermissionRequest) { - if (request.origin.toString().startsWith("file:/")) { - request.grant(request.resources) - } else { - Log.d(TAG, "Permission request from webview denied.") - request.deny() + try { + (staticWebView ?: WebView(androidAppContext)).apply { + layoutParams = ViewGroup.LayoutParams( + ViewGroup.LayoutParams.MATCH_PARENT, + ViewGroup.LayoutParams.MATCH_PARENT, + ) + this.webChromeClient = object: WebChromeClient() { + override fun onPermissionRequest(request: PermissionRequest) { + if (request.origin.toString().startsWith("file:/")) { + request.grant(request.resources) + } else { + Log.d(TAG, "Permission request from webview denied.") + request.deny() + } } } + this.webViewClient = LocalContentWebViewClient(webView, assetLoader) + this.clearHistory() + this.clearCache(true) + this.addJavascriptInterface(WebRTCInterface(onResponse), "WebRTCInterface") + this.setBackgroundColor(android.graphics.Color.BLACK) + val webViewSettings = this.settings + webViewSettings.allowFileAccess = true + webViewSettings.allowContentAccess = true + webViewSettings.javaScriptEnabled = true + webViewSettings.mediaPlaybackRequiresUserGesture = false + webViewSettings.cacheMode = WebSettings.LOAD_NO_CACHE + if (staticWebView == null) { + this.loadUrl("file:android_asset/www/android/call.html") + } else { + webView.value = this + } } - this.webViewClient = LocalContentWebViewClient(webView, assetLoader) - this.clearHistory() - this.clearCache(true) - this.addJavascriptInterface(WebRTCInterface(onResponse), "WebRTCInterface") - this.setBackgroundColor(android.graphics.Color.BLACK) - val webViewSettings = this.settings - webViewSettings.allowFileAccess = true - webViewSettings.allowContentAccess = true - webViewSettings.javaScriptEnabled = true - webViewSettings.mediaPlaybackRequiresUserGesture = false - webViewSettings.cacheMode = WebSettings.LOAD_NO_CACHE - if (staticWebView == null) { - this.loadUrl("file:android_asset/www/android/call.html") - } else { - webView.value = this - } + } catch (e: Exception) { + Log.e(TAG, "Error initializing WebView: ${e.stackTraceToString()}") + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), generalGetString(MR.strings.error_initializing_web_view).format(e.stackTraceToString())) + return@AndroidView View(androidAppContext) } } ) { /* WebView */ } 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 5a60e1d1b0..7cb5c77f6e 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 @@ -19,14 +19,10 @@ import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.draw.shadow -import androidx.compose.ui.graphics.* import androidx.compose.ui.layout.ContentScale import androidx.compose.ui.platform.LocalContext import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.unit.dp -import androidx.core.content.ContextCompat -import androidx.core.graphics.drawable.toBitmap -import chat.simplex.common.R import chat.simplex.common.model.* import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* @@ -34,7 +30,6 @@ import chat.simplex.common.model.ChatModel import chat.simplex.common.platform.* import chat.simplex.common.helpers.APPLICATION_ID import chat.simplex.common.helpers.saveAppLocale -import chat.simplex.common.views.usersettings.AppearanceScope.ColorEditor import chat.simplex.res.MR import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.compose.painterResource @@ -46,9 +41,8 @@ enum class AppIcon(val image: ImageResource) { } @Composable -actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) { +actual fun AppearanceView(m: ChatModel) { val appIcon = remember { mutableStateOf(findEnabledIcon()) } - fun setAppIcon(newIcon: AppIcon) { if (appIcon.value == newIcon) return val newComponent = ComponentName(APPLICATION_ID, "chat.simplex.app.MainActivity_${newIcon.name.lowercase()}") @@ -65,18 +59,11 @@ actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatMod appIcon.value = newIcon } - AppearanceScope.AppearanceLayout( appIcon, m.controller.appPrefs.appLanguage, m.controller.appPrefs.systemDarkTheme, changeIcon = ::setAppIcon, - showSettingsModal = showSettingsModal, - editColor = { name, initialColor -> - ModalManager.start.showModalCloseable { close -> - ColorEditor(name, initialColor, close) - } - }, ) } @@ -86,8 +73,6 @@ fun AppearanceScope.AppearanceLayout( languagePref: SharedPreference, systemDarkTheme: SharedPreference, changeIcon: (AppIcon) -> Unit, - showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - editColor: (ThemeColor, Color) -> Unit, ) { ColumnWithScrollBar( Modifier.fillMaxWidth(), @@ -120,6 +105,13 @@ fun AppearanceScope.AppearanceLayout( } // } } + + SectionDividerSpaced(maxTopPadding = true) + ThemesSection(systemDarkTheme) + + SectionDividerSpaced(maxTopPadding = true) + ProfileImageSection() + SectionDividerSpaced() SectionView(stringResource(MR.strings.settings_section_title_icon), padding = PaddingValues(horizontal = DEFAULT_PADDING_HALF)) { @@ -145,11 +137,6 @@ fun AppearanceScope.AppearanceLayout( } } - SectionDividerSpaced(maxTopPadding = true) - ProfileImageSection() - - SectionDividerSpaced(maxTopPadding = true) - ThemesSection(systemDarkTheme, showSettingsModal, editColor) SectionBottomSpacer() } } @@ -169,8 +156,6 @@ fun PreviewAppearanceSettings() { languagePref = SharedPreference({ null }, {}), systemDarkTheme = SharedPreference({ null }, {}), changeIcon = {}, - showSettingsModal = { {} }, - editColor = { _, _ -> }, ) } } 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 56487874ba..74a54e54cf 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 @@ -4,7 +4,7 @@ import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.snapshots.SnapshotStateList import androidx.compose.runtime.snapshots.SnapshotStateMap -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.* import androidx.compose.ui.text.SpanStyle import androidx.compose.ui.text.font.* import androidx.compose.ui.text.style.TextDecoration @@ -399,6 +399,18 @@ object ChatModel { currentUser.value = updated } + fun updateCurrentUserUiThemes(rhId: Long?, uiThemes: ThemeModeOverrides?) { + val current = currentUser.value ?: return + val updated = current.copy( + uiThemes = uiThemes + ) + val i = users.indexOfFirst { it.user.userId == current.userId && it.user.remoteHostId == rhId } + if (i != -1) { + users[i] = users[i].copy(user = updated) + } + currentUser.value = updated + } + suspend fun addLiveDummy(chatInfo: ChatInfo): ChatItem { val cItem = ChatItem.liveDummy(chatInfo is ChatInfo.Direct) withContext(Dispatchers.Main) { @@ -682,7 +694,8 @@ data class User( override val showNtfs: Boolean, val sendRcptsContacts: Boolean, val sendRcptsSmallGroups: Boolean, - val viewPwdHash: UserPwdHash? + val viewPwdHash: UserPwdHash?, + val uiThemes: ThemeModeOverrides? = null, ): NamedChat, UserLike { override val displayName: String get() = profile.displayName override val fullName: String get() = profile.fullName @@ -709,6 +722,7 @@ data class User( sendRcptsContacts = true, sendRcptsSmallGroups = false, viewPwdHash = null, + uiThemes = null, ) } } @@ -1041,7 +1055,8 @@ data class Contact( override val updatedAt: Instant, val chatTs: Instant?, val contactGroupMemberId: Long? = null, - val contactGrpInvSent: Boolean + val contactGrpInvSent: Boolean, + val uiThemes: ThemeModeOverrides? = null, ): SomeChat, NamedChat { override val chatType get() = ChatType.Direct override val id get() = "@$contactId" @@ -1113,7 +1128,8 @@ data class Contact( createdAt = Clock.System.now(), updatedAt = Clock.System.now(), chatTs = Clock.System.now(), - contactGrpInvSent = false + contactGrpInvSent = false, + uiThemes = null, ) } } @@ -1253,7 +1269,8 @@ data class GroupInfo ( val chatSettings: ChatSettings, override val createdAt: Instant, override val updatedAt: Instant, - val chatTs: Instant? + val chatTs: Instant?, + val uiThemes: ThemeModeOverrides? = null, ): SomeChat, NamedChat { override val chatType get() = ChatType.Group override val id get() = "#$groupId" @@ -1295,7 +1312,8 @@ data class GroupInfo ( chatSettings = ChatSettings(enableNtfs = MsgFilter.All, sendRcpts = null, favorite = false), createdAt = Clock.System.now(), updatedAt = Clock.System.now(), - chatTs = Clock.System.now() + chatTs = Clock.System.now(), + uiThemes = null, ) } } @@ -1915,12 +1933,13 @@ data class ChatItem ( itemDeleted: CIDeleted? = null, itemEdited: Boolean = false, itemTimed: CITimed? = null, + itemLive: Boolean = false, deletable: Boolean = true, editable: Boolean = true ) = ChatItem( chatDir = dir, - meta = CIMeta.getSample(id, ts, text, status, sentViaProxy, itemForwarded, itemDeleted, itemEdited, itemTimed, deletable, editable), + meta = CIMeta.getSample(id, ts, text, status, sentViaProxy, itemForwarded, itemDeleted, itemEdited, itemTimed, itemLive, deletable, editable), content = CIContent.SndMsgContent(msgContent = MsgContent.MCText(text)), quotedItem = quotedItem, reactions = listOf(), 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 ca237cd52f..1f7f55fb53 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/model/SimpleXAPI.kt @@ -26,8 +26,7 @@ import kotlinx.coroutines.sync.withLock import kotlinx.datetime.Clock import kotlinx.datetime.Instant import kotlinx.serialization.* -import kotlinx.serialization.builtins.MapSerializer -import kotlinx.serialization.builtins.serializer +import kotlinx.serialization.builtins.* import kotlinx.serialization.json.* import java.util.Date @@ -168,13 +167,20 @@ class AppPreferences { val selfDestruct = mkBoolPreference(SHARED_PREFS_SELF_DESTRUCT, false) val selfDestructDisplayName = mkStrPreference(SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME, null) - val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM.name) - val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.name) - val themeOverrides = mkMapPreference(SHARED_PREFS_THEMES, mapOf(), encode = { + val currentTheme = mkStrPreference(SHARED_PREFS_CURRENT_THEME, DefaultTheme.SYSTEM_THEME_NAME) + val systemDarkTheme = mkStrPreference(SHARED_PREFS_SYSTEM_DARK_THEME, DefaultTheme.SIMPLEX.themeName) + val currentThemeIds = mkMapPreference(SHARED_PREFS_CURRENT_THEME_IDs, mapOf(), encode = { + json.encodeToString(MapSerializer(String.serializer(), String.serializer()), it) + }, decode = { + json.decodeFromString(MapSerializer(String.serializer(), String.serializer()), it) + }) + // Deprecated. Remove key from preferences in 2025 + val themeOverridesOld = mkMapPreference(SHARED_PREFS_THEMES_OLD, mapOf(), encode = { json.encodeToString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it) }, decode = { - json.decodeFromString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it) + jsonCoerceInputValues.decodeFromString(MapSerializer(String.serializer(), ThemeOverrides.serializer()), it) }, settingsThemes) + val themeOverrides = mkThemeOverridesPreference() val profileImageCornerRadius = mkFloatPreference(SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS, 22.5f) val whatsNewVersion = mkStrPreference(SHARED_PREFS_WHATS_NEW_VERSION, null) @@ -269,6 +275,12 @@ class AppPreferences { set = fun(value) = prefs.putString(prefName, encode(value)) ) + private fun mkThemeOverridesPreference(): SharedPreference> = + SharedPreference( + get = fun() = themeOverridesStore ?: (readThemeOverrides()).also { themeOverridesStore = it }, + set = fun(value) { if (writeThemeOverrides(value)) { themeOverridesStore = value } } + ) + companion object { const val SHARED_PREFS_ID = "chat.simplex.app.SIMPLEX_APP_PREFS" internal const val SHARED_PREFS_THEMES_ID = "chat.simplex.app.THEMES" @@ -345,8 +357,10 @@ class AppPreferences { private const val SHARED_PREFS_SELF_DESTRUCT_DISPLAY_NAME = "LocalAuthenticationSelfDestructDisplayName" private const val SHARED_PREFS_PQ_EXPERIMENTAL_ENABLED = "PQExperimentalEnabled" // no longer used private const val SHARED_PREFS_CURRENT_THEME = "CurrentTheme" + private const val SHARED_PREFS_CURRENT_THEME_IDs = "CurrentThemeIds" private const val SHARED_PREFS_SYSTEM_DARK_THEME = "SystemDarkTheme" - private const val SHARED_PREFS_THEMES = "Themes" + private const val SHARED_PREFS_THEMES_OLD = "Themes" + private const val SHARED_PREFS_THEME_OVERRIDES = "ThemeOverrides" private const val SHARED_PREFS_PROFILE_IMAGE_CORNER_RADIUS = "ProfileImageCornerRadius" private const val SHARED_PREFS_WHATS_NEW_VERSION = "WhatsNewVersion" private const val SHARED_PREFS_LAST_MIGRATED_VERSION_CODE = "LastMigratedVersionCode" @@ -361,6 +375,8 @@ class AppPreferences { private const val SHARED_PREFS_IOS_CALL_KIT_ENABLED = "iOSCallKitEnabled" private const val SHARED_PREFS_IOS_CALL_KIT_CALLS_IN_RECENTS = "iOSCallKitCallsInRecents" + + private var themeOverridesStore: List? = null } } @@ -445,8 +461,13 @@ object ChatController { 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) + apiSetAppFilePaths( + getMigrationTempFilesDirectory().absolutePath, + getMigrationTempFilesDirectory().absolutePath, + wallpapersDir.parentFile.absolutePath, + remoteHostsDir.absolutePath, + ctrl + ) apiStartChat(ctrl) return migrationActiveUser } @@ -665,22 +686,10 @@ object ChatController { } } - suspend fun apiSetTempFolder(tempFolder: String, ctrl: ChatCtrl? = null) { - val r = sendCmd(null, CC.SetTempFolder(tempFolder), ctrl) + suspend fun apiSetAppFilePaths(filesFolder: String, tempFolder: String, assetsFolder: String, remoteHostsFolder: String, ctrl: ChatCtrl? = null) { + val r = sendCmd(null, CC.ApiSetAppFilePaths(filesFolder, tempFolder, assetsFolder, remoteHostsFolder), ctrl) if (r is CR.CmdOk) return - throw Exception("failed to set temp folder: ${r.responseType} ${r.details}") - } - - suspend fun apiSetFilesFolder(filesFolder: String, ctrl: ChatCtrl? = null) { - val r = sendCmd(null, CC.SetFilesFolder(filesFolder), ctrl) - if (r is CR.CmdOk) return - 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 Exception("failed to set remote hosts folder: ${r.responseType} ${r.details}") + throw Exception("failed to set app file paths: ${r.responseType} ${r.details}") } suspend fun apiSetEncryptLocalFiles(enable: Boolean) = sendCommandOkResp(null, CC.ApiSetEncryptLocalFiles(enable)) @@ -922,6 +931,20 @@ object ChatController { return null } + suspend fun apiContactQueueInfo(rh: Long?, contactId: Long): Pair? { + val r = sendCmd(rh, CC.APIContactQueueInfo(contactId)) + if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo) + apiErrorAlert("apiContactQueueInfo", generalGetString(MR.strings.error), r) + return null + } + + suspend fun apiGroupMemberQueueInfo(rh: Long?, groupId: Long, groupMemberId: Long): Pair? { + val r = sendCmd(rh, CC.APIGroupMemberQueueInfo(groupId, groupMemberId)) + if (r is CR.QueueInfoR) return Pair(r.rcvMsgInfo, r.queueInfo) + apiErrorAlert("apiGroupMemberQueueInfo", generalGetString(MR.strings.error), r) + return null + } + suspend fun apiSwitchContact(rh: Long?, contactId: Long): ConnectionStats? { val r = sendCmd(rh, CC.APISwitchContact(contactId)) if (r is CR.ContactSwitchStarted) return r.connectionStats @@ -1166,6 +1189,20 @@ object ChatController { return null } + suspend fun apiSetUserUIThemes(rh: Long?, userId: Long, themes: ThemeModeOverrides?): Boolean { + val r = sendCmd(rh, CC.ApiSetUserUIThemes(userId, themes)) + if (r is CR.CmdOk) return true + Log.e(TAG, "apiSetUserUIThemes bad response: ${r.responseType} ${r.details}") + return false + } + + suspend fun apiSetChatUIThemes(rh: Long?, chatId: ChatId, themes: ThemeModeOverrides?): Boolean { + val r = sendCmd(rh, CC.ApiSetChatUIThemes(chatId, themes)) + if (r is CR.CmdOk) return true + Log.e(TAG, "apiSetChatUIThemes bad response: ${r.responseType} ${r.details}") + return false + } + suspend fun apiCreateUserAddress(rh: Long?): String? { val userId = kotlin.runCatching { currentUserId("apiCreateUserAddress") }.getOrElse { return null } val r = sendCmd(rh, CC.ApiCreateMyAddress(userId)) @@ -2437,9 +2474,8 @@ sealed class CC { class ApiDeleteUser(val userId: Long, val delSMPQueues: Boolean, val viewPwd: String?): CC() class StartChat(val mainApp: Boolean): CC() class ApiStopChat: CC() - class SetTempFolder(val tempFolder: String): CC() - class SetFilesFolder(val filesFolder: String): CC() - class SetRemoteHostsFolder(val remoteHostsFolder: String): CC() + @Serializable + class ApiSetAppFilePaths(val appFilesFolder: String, val appTempFolder: String, val appAssetsFolder: String, val appRemoteHostsFolder: String): CC() class ApiSetEncryptLocalFiles(val enable: Boolean): CC() class ApiExportArchive(val config: ArchiveConfig): CC() class ApiImportArchive(val config: ArchiveConfig): CC() @@ -2485,6 +2521,8 @@ sealed class CC { class ApiSetMemberSettings(val groupId: Long, val groupMemberId: Long, val memberSettings: GroupMemberSettings): CC() class APIContactInfo(val contactId: Long): CC() class APIGroupMemberInfo(val groupId: Long, val groupMemberId: Long): CC() + class APIContactQueueInfo(val contactId: Long): CC() + class APIGroupMemberQueueInfo(val groupId: Long, val groupMemberId: Long): CC() class APISwitchContact(val contactId: Long): CC() class APISwitchGroupMember(val groupId: Long, val groupMemberId: Long): CC() class APIAbortSwitchContact(val contactId: Long): CC() @@ -2507,6 +2545,8 @@ sealed class CC { class ApiSetContactPrefs(val contactId: Long, val prefs: ChatPreferences): CC() class ApiSetContactAlias(val contactId: Long, val localAlias: String): CC() class ApiSetConnectionAlias(val connId: Long, val localAlias: String): CC() + class ApiSetUserUIThemes(val userId: Long, val themes: ThemeModeOverrides?): CC() + class ApiSetChatUIThemes(val chatId: String, val themes: ThemeModeOverrides?): CC() class ApiCreateMyAddress(val userId: Long): CC() class ApiDeleteMyAddress(val userId: Long): CC() class ApiShowMyAddress(val userId: Long): CC() @@ -2574,9 +2614,7 @@ sealed class CC { is ApiDeleteUser -> "/_delete user $userId del_smp=${onOff(delSMPQueues)}${maybePwd(viewPwd)}" is StartChat -> "/_start main=${onOff(mainApp)}" is ApiStopChat -> "/_stop" - is SetTempFolder -> "/_temp_folder $tempFolder" - is SetFilesFolder -> "/_files_folder $filesFolder" - is SetRemoteHostsFolder -> "/remote_hosts_folder $remoteHostsFolder" + is ApiSetAppFilePaths -> "/set file paths ${json.encodeToString(this)}" is ApiSetEncryptLocalFiles -> "/_files_encrypt ${onOff(enable)}" is ApiExportArchive -> "/_db export ${json.encodeToString(config)}" is ApiImportArchive -> "/_db import ${json.encodeToString(config)}" @@ -2630,6 +2668,8 @@ sealed class CC { is ApiSetMemberSettings -> "/_member settings #$groupId $groupMemberId ${json.encodeToString(memberSettings)}" is APIContactInfo -> "/_info @$contactId" is APIGroupMemberInfo -> "/_info #$groupId $groupMemberId" + is APIContactQueueInfo -> "/_queue info @$contactId" + is APIGroupMemberQueueInfo -> "/_queue info #$groupId $groupMemberId" is APISwitchContact -> "/_switch @$contactId" is APISwitchGroupMember -> "/_switch #$groupId $groupMemberId" is APIAbortSwitchContact -> "/_abort switch @$contactId" @@ -2656,6 +2696,8 @@ sealed class CC { is ApiSetContactPrefs -> "/_set prefs @$contactId ${json.encodeToString(prefs)}" is ApiSetContactAlias -> "/_set alias @$contactId ${localAlias.trim()}" is ApiSetConnectionAlias -> "/_set alias :$connId ${localAlias.trim()}" + is ApiSetUserUIThemes -> "/_set theme user $userId ${if (themes != null) json.encodeToString(themes) else ""}" + is ApiSetChatUIThemes -> "/_set theme $chatId ${if (themes != null) json.encodeToString(themes) else ""}" is ApiCreateMyAddress -> "/_address $userId" is ApiDeleteMyAddress -> "/_delete_address $userId" is ApiShowMyAddress -> "/_show_address $userId" @@ -2720,9 +2762,7 @@ sealed class CC { is ApiDeleteUser -> "apiDeleteUser" is StartChat -> "startChat" is ApiStopChat -> "apiStopChat" - is SetTempFolder -> "setTempFolder" - is SetFilesFolder -> "setFilesFolder" - is SetRemoteHostsFolder -> "setRemoteHostsFolder" + is ApiSetAppFilePaths -> "apiSetAppFilePaths" is ApiSetEncryptLocalFiles -> "apiSetEncryptLocalFiles" is ApiExportArchive -> "apiExportArchive" is ApiImportArchive -> "apiImportArchive" @@ -2768,6 +2808,8 @@ sealed class CC { is ApiSetMemberSettings -> "apiSetMemberSettings" is APIContactInfo -> "apiContactInfo" is APIGroupMemberInfo -> "apiGroupMemberInfo" + is APIContactQueueInfo -> "apiContactQueueInfo" + is APIGroupMemberQueueInfo -> "apiGroupMemberQueueInfo" is APISwitchContact -> "apiSwitchContact" is APISwitchGroupMember -> "apiSwitchGroupMember" is APIAbortSwitchContact -> "apiAbortSwitchContact" @@ -2790,6 +2832,8 @@ sealed class CC { is ApiSetContactPrefs -> "apiSetContactPrefs" is ApiSetContactAlias -> "apiSetContactAlias" is ApiSetConnectionAlias -> "apiSetConnectionAlias" + is ApiSetUserUIThemes -> "apiSetUserUIThemes" + is ApiSetChatUIThemes -> "apiSetChatUIThemes" is ApiCreateMyAddress -> "apiCreateMyAddress" is ApiDeleteMyAddress -> "apiDeleteMyAddress" is ApiShowMyAddress -> "apiShowMyAddress" @@ -4061,6 +4105,15 @@ val json = Json { explicitNulls = false } +// Can decode unknown enum to default value specified for this field +val jsonCoerceInputValues = Json { + prettyPrint = true + ignoreUnknownKeys = true + encodeDefaults = true + explicitNulls = false + coerceInputValues = true +} + val jsonShort = Json { prettyPrint = false ignoreUnknownKeys = true @@ -4071,6 +4124,8 @@ val jsonShort = Json { val yaml = Yaml(configuration = YamlConfiguration( strictMode = false, encodeDefaults = false, + /** ~5.5 MB limitation since wallpaper is limited by 5 MB, see [saveWallpaperFile] */ + codePointLimit = 5500000, )) @Serializable @@ -4162,6 +4217,7 @@ sealed class CR { @Serializable @SerialName("networkConfig") class NetworkConfig(val networkConfig: NetCfg): CR() @Serializable @SerialName("contactInfo") class ContactInfo(val user: UserRef, val contact: Contact, val connectionStats_: ConnectionStats? = null, val customUserProfile: Profile? = null): CR() @Serializable @SerialName("groupMemberInfo") class GroupMemberInfo(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats_: ConnectionStats? = null): CR() + @Serializable @SerialName("queueInfo") class QueueInfoR(val user: UserRef, val rcvMsgInfo: RcvMsgInfo?, val queueInfo: QueueInfo): CR() @Serializable @SerialName("contactSwitchStarted") class ContactSwitchStarted(val user: UserRef, val contact: Contact, val connectionStats: ConnectionStats): CR() @Serializable @SerialName("groupMemberSwitchStarted") class GroupMemberSwitchStarted(val user: UserRef, val groupInfo: GroupInfo, val member: GroupMember, val connectionStats: ConnectionStats): CR() @Serializable @SerialName("contactSwitchAborted") class ContactSwitchAborted(val user: UserRef, val contact: Contact, val connectionStats: ConnectionStats): CR() @@ -4333,6 +4389,7 @@ sealed class CR { is NetworkConfig -> "networkConfig" is ContactInfo -> "contactInfo" is GroupMemberInfo -> "groupMemberInfo" + is QueueInfoR -> "queueInfo" is ContactSwitchStarted -> "contactSwitchStarted" is GroupMemberSwitchStarted -> "groupMemberSwitchStarted" is ContactSwitchAborted -> "contactSwitchAborted" @@ -4494,6 +4551,7 @@ sealed class CR { is NetworkConfig -> json.encodeToString(networkConfig) is ContactInfo -> withUser(user, "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats_)}") is GroupMemberInfo -> withUser(user, "group: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}\nconnectionStats: ${json.encodeToString(connectionStats_)}") + is QueueInfoR -> withUser(user, "rcvMsgInfo: ${json.encodeToString(rcvMsgInfo)}\nqueueInfo: ${json.encodeToString(queueInfo)}\n") is ContactSwitchStarted -> withUser(user, "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats)}") is GroupMemberSwitchStarted -> withUser(user, "group: ${json.encodeToString(groupInfo)}\nmember: ${json.encodeToString(member)}\nconnectionStats: ${json.encodeToString(connectionStats)}") is ContactSwitchAborted -> withUser(user, "contact: ${json.encodeToString(contact)}\nconnectionStats: ${json.encodeToString(connectionStats)}") @@ -4907,7 +4965,6 @@ sealed class ChatErrorType { is GroupMemberNotActive -> "groupMemberNotActive" is GroupMemberUserRemoved -> "groupMemberUserRemoved" is GroupMemberNotFound -> "groupMemberNotFound" - is GroupMemberIntroNotFound -> "groupMemberIntroNotFound" is GroupCantResendInvitation -> "groupCantResendInvitation" is GroupInternal -> "groupInternal" is FileNotFound -> "fileNotFound" @@ -4987,7 +5044,6 @@ sealed class ChatErrorType { @Serializable @SerialName("groupMemberNotActive") object GroupMemberNotActive: ChatErrorType() @Serializable @SerialName("groupMemberUserRemoved") object GroupMemberUserRemoved: ChatErrorType() @Serializable @SerialName("groupMemberNotFound") object GroupMemberNotFound: ChatErrorType() - @Serializable @SerialName("groupMemberIntroNotFound") class GroupMemberIntroNotFound(val contactName: String): ChatErrorType() @Serializable @SerialName("groupCantResendInvitation") class GroupCantResendInvitation(val groupInfo: GroupInfo, val contactName: String): ChatErrorType() @Serializable @SerialName("groupInternal") class GroupInternal(val message: String): ChatErrorType() @Serializable @SerialName("fileNotFound") class FileNotFound(val message: String): ChatErrorType() @@ -5521,6 +5577,11 @@ data class AppSettings( var androidCallOnLockScreen: AppSettingsLockScreenCalls? = null, var iosCallKitEnabled: Boolean? = null, var iosCallKitCallsInRecents: Boolean? = null, + var uiProfileImageCornerRadius: Float? = null, + var uiColorScheme: String? = null, + var uiDarkColorScheme: String? = null, + var uiCurrentThemeIds: Map? = null, + var uiThemes: List? = null, ) { fun prepareForExport(): AppSettings { val empty = AppSettings() @@ -5545,6 +5606,11 @@ data class AppSettings( if (androidCallOnLockScreen != def.androidCallOnLockScreen) { empty.androidCallOnLockScreen = androidCallOnLockScreen } if (iosCallKitEnabled != def.iosCallKitEnabled) { empty.iosCallKitEnabled = iosCallKitEnabled } if (iosCallKitCallsInRecents != def.iosCallKitCallsInRecents) { empty.iosCallKitCallsInRecents = iosCallKitCallsInRecents } + if (uiProfileImageCornerRadius != def.uiProfileImageCornerRadius) { empty.uiProfileImageCornerRadius = uiProfileImageCornerRadius } + if (uiColorScheme != def.uiColorScheme) { empty.uiColorScheme = uiColorScheme } + if (uiDarkColorScheme != def.uiDarkColorScheme) { empty.uiDarkColorScheme = uiDarkColorScheme } + if (uiCurrentThemeIds != def.uiCurrentThemeIds) { empty.uiCurrentThemeIds = uiCurrentThemeIds } + if (uiThemes != def.uiThemes) { empty.uiThemes = uiThemes } return empty } @@ -5577,6 +5643,11 @@ data class AppSettings( androidCallOnLockScreen?.let { def.callOnLockScreen.set(it.toCallOnLockScreen()) } iosCallKitEnabled?.let { def.iosCallKitEnabled.set(it) } iosCallKitCallsInRecents?.let { def.iosCallKitCallsInRecents.set(it) } + uiProfileImageCornerRadius?.let { def.profileImageCornerRadius.set(it) } + uiColorScheme?.let { def.currentTheme.set(it) } + uiDarkColorScheme?.let { def.systemDarkTheme.set(it) } + uiCurrentThemeIds?.let { def.currentThemeIds.set(it) } + uiThemes?.let { def.themeOverrides.set(it.skipDuplicates()) } } companion object { @@ -5601,7 +5672,12 @@ data class AppSettings( confirmDBUpgrades = false, androidCallOnLockScreen = AppSettingsLockScreenCalls.SHOW, iosCallKitEnabled = true, - iosCallKitCallsInRecents = false + iosCallKitCallsInRecents = false, + uiProfileImageCornerRadius = 22.5f, + uiColorScheme = DefaultTheme.SYSTEM_THEME_NAME, + uiDarkColorScheme = DefaultTheme.SIMPLEX.themeName, + uiCurrentThemeIds = null, + uiThemes = null, ) val current: AppSettings @@ -5628,6 +5704,11 @@ data class AppSettings( androidCallOnLockScreen = AppSettingsLockScreenCalls.from(def.callOnLockScreen.get()), iosCallKitEnabled = def.iosCallKitEnabled.get(), iosCallKitCallsInRecents = def.iosCallKitCallsInRecents.get(), + uiProfileImageCornerRadius = def.profileImageCornerRadius.get(), + uiColorScheme = def.currentTheme.get() ?: DefaultTheme.SYSTEM_THEME_NAME, + uiDarkColorScheme = def.systemDarkTheme.get() ?: DefaultTheme.SIMPLEX.themeName, + uiCurrentThemeIds = def.currentThemeIds.get(), + uiThemes = def.themeOverrides.get(), ) } } @@ -5728,3 +5809,52 @@ enum class UserNetworkType { OTHER -> generalGetString(MR.strings.network_type_other) } } + +@Serializable +data class RcvMsgInfo ( + val msgId: Long, + val msgDeliveryId: Long, + val msgDeliveryStatus: String, + val agentMsgId: Long, + val agentMsgMeta: String +) + +@Serializable +data class QueueInfo ( + val qiSnd: Boolean, + val qiNtf: Boolean, + val qiSub: QSub? = null, + val qiSize: Int, + val qiMsg: MsgInfo? = null +) + +@Serializable +data class QSub ( + val qSubThread: QSubThread, + val qDelivered: String? = null +) + +enum class QSubThread { + @SerialName("noSub") + NO_SUB, + @SerialName("subPending") + SUB_PENDING, + @SerialName("subThread") + SUB_THREAD, + @SerialName("prohibitSub") + PROHIBIT_SUB +} + +@Serializable +data class MsgInfo ( + val msgId: String, + val msgTs: Instant, + val msgType: MsgType, +) + +enum class MsgType { + @SerialName("message") + MESSAGE, + @SerialName("quota") + QUOTA +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/other/wheel-picker/FWheelPickerDefault.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/other/wheel-picker/FWheelPickerDefault.kt index 9760e9c9f2..052e388f97 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/other/wheel-picker/FWheelPickerDefault.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/other/wheel-picker/FWheelPickerDefault.kt @@ -2,7 +2,6 @@ package com.sd.lib.compose.wheel_picker import androidx.compose.animation.core.animateFloatAsState import androidx.compose.foundation.background -import chat.simplex.common.ui.theme.isSystemInDarkTheme import androidx.compose.foundation.layout.* import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue @@ -12,6 +11,7 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.graphicsLayer import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import chat.simplex.common.ui.theme.isInDarkTheme /** * The default implementation of focus view in vertical. @@ -76,7 +76,7 @@ fun FWheelPickerFocusHorizontal( */ private val DefaultDividerColor: Color @Composable - get() = (if (isSystemInDarkTheme()) { + get() = (if (isInDarkTheme()) { Color.White } else { Color.Black diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt index 7d5b1b0196..10cb17df1d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt @@ -42,6 +42,12 @@ fun runMigrations() { ChatController.appPrefs.currentTheme.set(DefaultTheme.SIMPLEX.name) } lastMigration.set(117) + } else if (lastMigration.get() < 203) { + // Moving to a different key for storing themes as a List + val oldOverrides = ChatController.appPrefs.themeOverridesOld.get().values.toList() + ChatController.appPrefs.themeOverrides.set(oldOverrides) + ChatController.appPrefs.currentThemeIds.set(oldOverrides.associate { it.base.themeName to it.themeId }) + lastMigration.set(203) } else { lastMigration.set(BuildConfigCommon.ANDROID_VERSION_CODE) break 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 0d447a4a5a..85179d66c7 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 @@ -43,14 +43,13 @@ val appPreferences: AppPreferences val chatController: ChatController = ChatController -fun initChatControllerAndRunMigrations() { +fun initChatControllerOnStart() { withLongRunningApi { if (appPreferences.chatStopped.get() && appPreferences.storeDBPassphrase.get() && ksDatabasePassword.get() != null) { initChatController(startChat = ::showStartChatAfterRestartAlert) } else { initChatController() } - runMigrations() } } @@ -88,11 +87,13 @@ suspend fun initChatController(useKey: String? = null, confirmMigrations: Migrat return } platform.androidRestartNetworkObserver() - controller.apiSetTempFolder(coreTmpDir.absolutePath) - controller.apiSetFilesFolder(appFilesDir.absolutePath) - if (appPlatform.isDesktop) { - controller.apiSetRemoteHostsFolder(remoteHostsDir.absolutePath) - } + controller.apiSetAppFilePaths( + appFilesDir.absolutePath, + coreTmpDir.absolutePath, + wallpapersDir.parentFile.absolutePath, + remoteHostsDir.absolutePath, + ctrl + ) controller.apiSetEncryptLocalFiles(controller.appPrefs.privacyEncryptLocalFiles.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) 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 c788a6902e..250afe03c4 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 @@ -1,10 +1,12 @@ package chat.simplex.common.platform import androidx.compose.runtime.Composable -import chat.simplex.common.model.CIFile -import chat.simplex.common.model.CryptoFile +import chat.simplex.common.model.* +import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.generalGetString import chat.simplex.res.MR +import com.charleskorn.kaml.* +import kotlinx.serialization.encodeToString import java.io.* import java.net.URI import java.net.URLDecoder @@ -14,8 +16,10 @@ expect val dataDir: File expect val tmpDir: File expect val filesDir: File expect val appFilesDir: File +expect val wallpapersDir: File expect val coreTmpDir: File expect val dbAbsolutePrefixPath: String +expect val preferencesDir: File expect val chatDatabaseFileName: String expect val agentDatabaseFileName: String @@ -78,6 +82,20 @@ fun getAppFilePath(fileName: String): String { } } +fun getWallpaperFilePath(fileName: String): String { + val rh = chatModel.currentRemoteHost.value + val s = File.separator + val path = if (rh == null) { + wallpapersDir.absolutePath + s + fileName + } else { + remoteHostsDir.absolutePath + s + rh.storePath + s + "simplex_v1_assets" + s + "wallpapers" + s + fileName + } + File(path).parentFile.mkdirs() + return path +} + +fun getPreferenceFilePath(fileName: String = "themes.yaml"): String = preferencesDir.absolutePath + File.separator + fileName + fun getLoadedFilePath(file: CIFile?): String? { val f = file?.fileSource?.filePath return if (f != null && file.loaded) { @@ -98,6 +116,42 @@ fun getLoadedFileSource(file: CIFile?): CryptoFile? { } } +fun readThemeOverrides(): List { + return try { + val file = File(getPreferenceFilePath("themes.yaml")) + if (!file.exists()) return emptyList() + + file.inputStream().use { + val map = yaml.parseToYamlNode(it).yamlMap + val list = map.get("themes") + val res = ArrayList() + list?.items?.forEach { + try { + res.add(yaml.decodeFromYamlNode(ThemeOverrides.serializer(), it)) + } catch (e: Throwable) { + Log.e(TAG, "Error while reading specific theme: ${e.stackTraceToString()}") + } + } + res.skipDuplicates() + } + } catch (e: Throwable) { + Log.e(TAG, "Error while reading themes file: ${e.stackTraceToString()}") + emptyList() + } +} + +fun writeThemeOverrides(overrides: List): Boolean = + try { + File(getPreferenceFilePath("themes.yaml")).outputStream().use { + val string = yaml.encodeToString(ThemesFile(themes = overrides)) + it.bufferedWriter().use { it.write(string) } + } + true + } catch (e: Throwable) { + Log.e(TAG, "Error while writing themes file: ${e.stackTraceToString()}") + false + } + private fun fileReady(file: CIFile, filePath: String) = File(filePath).exists() && CIFile.cachedRemoteFileRequests[file.fileSource] != false diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt index 2ee668fb23..8e45ded4f0 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/Resources.kt @@ -1,11 +1,13 @@ package chat.simplex.common.platform import androidx.compose.runtime.Composable +import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.Dp import com.russhwolf.settings.Settings +import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource @Composable @@ -31,3 +33,5 @@ expect fun windowWidth(): Dp expect fun desktopExpandWindowToWidth(width: Dp) expect fun isRtl(text: CharSequence): Boolean + +expect fun ImageResource.toComposeImageBitmap(): ImageBitmap? diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt index 5eeedbb2a0..c50ea5c349 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Color.kt @@ -31,17 +31,6 @@ val WarningOrange = Color(255, 127, 0, 255) val WarningYellow = Color(255, 192, 0, 255) val FileLight = Color(183, 190, 199, 255) val FileDark = Color(101, 101, 106, 255) -val SentMessageColor = Color(0x1E45B8FF) val MenuTextColor: Color @Composable get () = if (isInDarkTheme()) LocalContentColor.current.copy(alpha = 0.8f) else Color.Black -val NoteFolderIconColor: Color @Composable get() = with(CurrentColors.collectAsState().value.appColors.sentMessage) { - // Default color looks too light and better to have it here a little bit brighter - if (alpha == SentMessageColor.alpha) { - copy(min(SentMessageColor.alpha + 0.1f, 1f)) - } else { - // Color is non-standard and theme maker can choose color without alpha at all since the theme bound to dark/light variant, - // and it shouldn't be universal - this - } -} - +val NoteFolderIconColor: Color @Composable get() = MaterialTheme.appColors.primaryVariant2 diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt index 62acc13bfe..45f656a011 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/Theme.kt @@ -6,53 +6,136 @@ import androidx.compose.runtime.* import androidx.compose.ui.Modifier import androidx.compose.ui.geometry.Offset import androidx.compose.ui.graphics.* -import androidx.compose.ui.text.TextStyle import androidx.compose.ui.unit.dp import chat.simplex.common.model.ChatController -import chat.simplex.common.platform.isInNightMode +import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.ThemeManager.colorFromReadableHex +import chat.simplex.common.ui.theme.ThemeManager.toReadableHex import chat.simplex.common.views.helpers.* import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.serialization.SerialName import kotlinx.serialization.Serializable import chat.simplex.res.MR +import kotlinx.serialization.Transient +import java.util.UUID enum class DefaultTheme { - SYSTEM, LIGHT, DARK, SIMPLEX; + LIGHT, DARK, SIMPLEX, BLACK; + + companion object { + const val SYSTEM_THEME_NAME: String = "SYSTEM" + } + + val themeName: String + get() = name + + val mode: DefaultThemeMode get() = if (this == LIGHT) DefaultThemeMode.LIGHT else DefaultThemeMode.DARK // Call it only with base theme, not SYSTEM - fun hasChangedAnyColor(colors: Colors, appColors: AppColors): Boolean { - val palette = when (this) { - SYSTEM -> return false - LIGHT -> LightColorPalette - DARK -> DarkColorPalette - SIMPLEX -> SimplexColorPalette - } - val appPalette = when (this) { - SYSTEM -> return false - LIGHT -> LightColorPaletteApp - DARK -> DarkColorPaletteApp - SIMPLEX -> SimplexColorPaletteApp - } - return colors.primary != palette.primary || - colors.primaryVariant != palette.primaryVariant || - colors.secondary != palette.secondary || - colors.secondaryVariant != palette.secondaryVariant || - colors.background != palette.background || - colors.surface != palette.surface || - appColors != appPalette + fun hasChangedAnyColor(overrides: ThemeOverrides?): Boolean { + if (overrides == null) return false + return overrides.colors != ThemeColors() || + overrides.wallpaper != null && (overrides.wallpaper.background != null || overrides.wallpaper.tint != null) } } -data class AppColors( - val title: Color, - val sentMessage: Color, - val receivedMessage: Color -) +@Serializable +enum class DefaultThemeMode { + @SerialName("light") LIGHT, + @SerialName("dark") DARK +} + +@Stable +class AppColors( + title: Color, + primaryVariant2: Color, + sentMessage: Color, + sentQuote: Color, + receivedMessage: Color, + receivedQuote: Color, +) { + var title by mutableStateOf(title, structuralEqualityPolicy()) + internal set + var primaryVariant2 by mutableStateOf(primaryVariant2, structuralEqualityPolicy()) + internal set + var sentMessage by mutableStateOf(sentMessage, structuralEqualityPolicy()) + internal set + var sentQuote by mutableStateOf(sentQuote, structuralEqualityPolicy()) + internal set + var receivedMessage by mutableStateOf(receivedMessage, structuralEqualityPolicy()) + internal set + var receivedQuote by mutableStateOf(receivedQuote, structuralEqualityPolicy()) + internal set + + fun copy( + title: Color = this.title, + primaryVariant2: Color = this.primaryVariant2, + sentMessage: Color = this.sentMessage, + sentQuote: Color = this.sentQuote, + receivedMessage: Color = this.receivedMessage, + receivedQuote: Color = this.receivedQuote, + ): AppColors = AppColors( + title, + primaryVariant2, + sentMessage, + sentQuote, + receivedMessage, + receivedQuote, + ) + + override fun toString(): String { + return buildString { + append("AppColors(") + append("title=$title, ") + append("primaryVariant2=$primaryVariant2, ") + append("sentMessage=$sentMessage, ") + append("sentQuote=$sentQuote, ") + append("receivedMessage=$receivedMessage, ") + append("receivedQuote=$receivedQuote") + append(")") + } + } +} + +@Stable +class AppWallpaper( + background: Color? = null, + tint: Color? = null, + type: WallpaperType = WallpaperType.Empty, +) { + var background by mutableStateOf(background, structuralEqualityPolicy()) + internal set + var tint by mutableStateOf(tint, structuralEqualityPolicy()) + internal set + var type by mutableStateOf(type, structuralEqualityPolicy()) + internal set + + fun copy( + background: Color? = this.background, + tint: Color? = this.tint, + type: WallpaperType = this.type, + ): AppWallpaper = AppWallpaper( + background, + tint, + type, + ) + + override fun toString(): String { + return buildString { + append("AppWallpaper(") + append("background=$background, ") + append("tint=$tint, ") + append("type=$type") + append(")") + } + } +} enum class ThemeColor { - PRIMARY, PRIMARY_VARIANT, SECONDARY, SECONDARY_VARIANT, BACKGROUND, SURFACE, TITLE, SENT_MESSAGE, RECEIVED_MESSAGE; + PRIMARY, PRIMARY_VARIANT, SECONDARY, SECONDARY_VARIANT, BACKGROUND, SURFACE, TITLE, SENT_MESSAGE, SENT_QUOTE, RECEIVED_MESSAGE, RECEIVED_QUOTE, PRIMARY_VARIANT2, WALLPAPER_BACKGROUND, WALLPAPER_TINT; - fun fromColors(colors: Colors, appColors: AppColors): Color { + fun fromColors(colors: Colors, appColors: AppColors, appWallpaper: AppWallpaper): Color? { return when (this) { PRIMARY -> colors.primary PRIMARY_VARIANT -> colors.primaryVariant @@ -61,8 +144,13 @@ enum class ThemeColor { BACKGROUND -> colors.background SURFACE -> colors.surface TITLE -> appColors.title + PRIMARY_VARIANT2 -> appColors.primaryVariant2 SENT_MESSAGE -> appColors.sentMessage + SENT_QUOTE -> appColors.sentQuote RECEIVED_MESSAGE -> appColors.receivedMessage + RECEIVED_QUOTE -> appColors.receivedQuote + WALLPAPER_BACKGROUND -> appWallpaper.background + WALLPAPER_TINT -> appWallpaper.tint } } @@ -75,8 +163,13 @@ enum class ThemeColor { BACKGROUND -> generalGetString(MR.strings.color_background) SURFACE -> generalGetString(MR.strings.color_surface) TITLE -> generalGetString(MR.strings.color_title) + PRIMARY_VARIANT2 -> generalGetString(MR.strings.color_primary_variant2) SENT_MESSAGE -> generalGetString(MR.strings.color_sent_message) + SENT_QUOTE -> generalGetString(MR.strings.color_sent_quote) RECEIVED_MESSAGE -> generalGetString(MR.strings.color_received_message) + RECEIVED_QUOTE -> generalGetString(MR.strings.color_received_quote) + WALLPAPER_BACKGROUND -> generalGetString(MR.strings.color_wallpaper_background) + WALLPAPER_TINT -> generalGetString(MR.strings.color_wallpaper_tint) } } @@ -92,45 +185,232 @@ data class ThemeColors( @SerialName("menus") val surface: String? = null, val title: String? = null, + @SerialName("accentVariant2") + val primaryVariant2: String? = null, val sentMessage: String? = null, + @SerialName("sentReply") + val sentQuote: String? = null, val receivedMessage: String? = null, + @SerialName("receivedReply") + val receivedQuote: String? = null, ) { - fun toColors(base: DefaultTheme): Colors { + companion object { + fun from(colors: Colors, appColors: AppColors): ThemeColors = + ThemeColors( + primary = colors.primary.toReadableHex(), + primaryVariant = colors.primaryVariant.toReadableHex(), + secondary = colors.secondary.toReadableHex(), + secondaryVariant = colors.secondaryVariant.toReadableHex(), + background = colors.background.toReadableHex(), + surface = colors.surface.toReadableHex(), + title = appColors.title.toReadableHex(), + primaryVariant2 = appColors.primaryVariant2.toReadableHex(), + sentMessage = appColors.sentMessage.toReadableHex(), + sentQuote = appColors.sentQuote.toReadableHex(), + receivedMessage = appColors.receivedMessage.toReadableHex(), + receivedQuote = appColors.receivedQuote.toReadableHex(), + ) + } +} + +@Serializable +data class ThemeWallpaper ( + val preset: String? = null, + val scale: Float? = null, + val scaleType: WallpaperScaleType? = null, + val background: String? = null, + val tint: String? = null, + val image: String? = null, + val imageFile: String? = null, +) { + fun toAppWallpaper(): AppWallpaper { + return AppWallpaper( + background = background?.colorFromReadableHex(), + tint = tint?.colorFromReadableHex(), + type = WallpaperType.from(this) ?: WallpaperType.Empty + ) + } + + fun withFilledWallpaperBase64(): ThemeWallpaper { + val aw = toAppWallpaper() + val type = aw.type + return ThemeWallpaper( + image = if (type is WallpaperType.Image && type.image != null) resizeImageToStrSize(type.image!!, 5_000_000) else null, + imageFile = null, + preset = if (type is WallpaperType.Preset) type.filename else null, + scale = if (type is WallpaperType.Preset) type.scale else if (type is WallpaperType.Image) type.scale else 1f, + scaleType = if (type is WallpaperType.Image) type.scaleType else null, + background = aw.background?.toReadableHex(), + tint = aw.tint?.toReadableHex(), + ) + } + + fun withFilledWallpaperPath(): ThemeWallpaper { + val aw = toAppWallpaper() + val type = aw.type + return ThemeWallpaper( + image = null, + imageFile = if (type is WallpaperType.Image) type.filename else null, + preset = if (type is WallpaperType.Preset) type.filename else null, + scale = if (scale == null) null else if (type is WallpaperType.Preset) type.scale else if (type is WallpaperType.Image) scale else null, + scaleType = if (scaleType == null) null else if (type is WallpaperType.Image) type.scaleType else null, + background = aw.background?.toReadableHex(), + tint = aw.tint?.toReadableHex(), + ) + } + + fun importFromString(): ThemeWallpaper = + if (preset == null && image != null) { + // Need to save image from string and to save its path + try { + val parsed = base64ToBitmap(image) + val filename = saveWallpaperFile(parsed) + copy(image = null, imageFile = filename) + } catch (e: Exception) { + Log.e(TAG, "Error while parsing/copying the image: ${e.stackTraceToString()}") + ThemeWallpaper() + } + } else this + + companion object { + fun from(type: WallpaperType, background: String?, tint: String?): ThemeWallpaper { + return ThemeWallpaper( + image = null, + imageFile = if (type is WallpaperType.Image) type.filename else null, + preset = if (type is WallpaperType.Preset) type.filename else null, + scale = if (type is WallpaperType.Preset) type.scale else if (type is WallpaperType.Image) type.scale else null, + scaleType = if (type is WallpaperType.Image) type.scaleType else null, + background = background, + tint = tint, + ) + } + } +} + +@Serializable +data class ThemesFile( + val themes: List = emptyList() +) + +@Serializable +data class ThemeOverrides ( + val themeId: String = UUID.randomUUID().toString(), + val base: DefaultTheme, + val colors: ThemeColors = ThemeColors(), + val wallpaper: ThemeWallpaper? = null, +) { + + fun isSame(type: WallpaperType?, themeName: String): Boolean = + ( + (wallpaper?.preset != null && type is WallpaperType.Preset && wallpaper.preset == type.filename) || + (wallpaper?.imageFile != null && type is WallpaperType.Image) || + (wallpaper?.preset == null && wallpaper?.imageFile == null && (type == WallpaperType.Empty || type == null)) + ) && base.themeName == themeName + + fun withUpdatedColor(name: ThemeColor, color: String?): ThemeOverrides { + return copy( + colors = when (name) { + ThemeColor.PRIMARY -> colors.copy(primary = color) + ThemeColor.PRIMARY_VARIANT -> colors.copy(primaryVariant = color) + ThemeColor.SECONDARY -> colors.copy(secondary = color) + ThemeColor.SECONDARY_VARIANT -> colors.copy(secondaryVariant = color) + ThemeColor.BACKGROUND -> colors.copy(background = color) + ThemeColor.SURFACE -> colors.copy(surface = color) + ThemeColor.TITLE -> colors.copy(title = color) + ThemeColor.PRIMARY_VARIANT2 -> colors.copy(primaryVariant2 = color) + ThemeColor.SENT_MESSAGE -> colors.copy(sentMessage = color) + ThemeColor.SENT_QUOTE -> colors.copy(sentQuote = color) + ThemeColor.RECEIVED_MESSAGE -> colors.copy(receivedMessage = color) + ThemeColor.RECEIVED_QUOTE -> colors.copy(receivedQuote = color) + ThemeColor.WALLPAPER_BACKGROUND -> colors.copy() + ThemeColor.WALLPAPER_TINT -> colors.copy() + }, wallpaper = when (name) { + ThemeColor.WALLPAPER_BACKGROUND -> wallpaper?.copy(background = color) + ThemeColor.WALLPAPER_TINT -> wallpaper?.copy(tint = color) + else -> wallpaper?.copy() + } + ) + } + + fun toColors(base: DefaultTheme, perChatTheme: ThemeColors?, perUserTheme: ThemeColors?, presetWallpaperTheme: ThemeColors?): Colors { val baseColors = when (base) { DefaultTheme.LIGHT -> LightColorPalette DefaultTheme.DARK -> DarkColorPalette DefaultTheme.SIMPLEX -> SimplexColorPalette - // shouldn't be here - DefaultTheme.SYSTEM -> LightColorPalette + DefaultTheme.BLACK -> BlackColorPalette } return baseColors.copy( - primary = primary?.colorFromReadableHex() ?: baseColors.primary, - primaryVariant = primaryVariant?.colorFromReadableHex() ?: baseColors.primaryVariant, - secondary = secondary?.colorFromReadableHex() ?: baseColors.secondary, - secondaryVariant = secondaryVariant?.colorFromReadableHex() ?: baseColors.secondaryVariant, - background = background?.colorFromReadableHex() ?: baseColors.background, - surface = surface?.colorFromReadableHex() ?: baseColors.surface, + primary = perChatTheme?.primary?.colorFromReadableHex() ?: perUserTheme?.primary?.colorFromReadableHex() ?: colors.primary?.colorFromReadableHex() ?: presetWallpaperTheme?.primary?.colorFromReadableHex() ?: baseColors.primary, + primaryVariant = perChatTheme?.primaryVariant?.colorFromReadableHex() ?: perUserTheme?.primaryVariant?.colorFromReadableHex() ?: colors.primaryVariant?.colorFromReadableHex() ?: presetWallpaperTheme?.primaryVariant?.colorFromReadableHex() ?: baseColors.primaryVariant, + secondary = perChatTheme?.secondary?.colorFromReadableHex() ?: perUserTheme?.secondary?.colorFromReadableHex() ?: colors.secondary?.colorFromReadableHex() ?: presetWallpaperTheme?.secondary?.colorFromReadableHex() ?: baseColors.secondary, + secondaryVariant = perChatTheme?.secondaryVariant?.colorFromReadableHex() ?: perUserTheme?.secondaryVariant?.colorFromReadableHex() ?: colors.secondaryVariant?.colorFromReadableHex() ?: presetWallpaperTheme?.secondaryVariant?.colorFromReadableHex() ?: baseColors.secondaryVariant, + background = perChatTheme?.background?.colorFromReadableHex() ?: perUserTheme?.background?.colorFromReadableHex() ?: colors.background?.colorFromReadableHex() ?: presetWallpaperTheme?.background?.colorFromReadableHex() ?: baseColors.background, + surface = perChatTheme?.surface?.colorFromReadableHex() ?: perUserTheme?.surface?.colorFromReadableHex() ?: colors.surface?.colorFromReadableHex() ?: presetWallpaperTheme?.surface?.colorFromReadableHex() ?: baseColors.surface, ) } - fun toAppColors(base: DefaultTheme): AppColors { + fun toAppColors(base: DefaultTheme, perChatTheme: ThemeColors?, perChatWallpaperType: WallpaperType?, perUserTheme: ThemeColors?, perUserWallpaperType: WallpaperType?, presetWallpaperTheme: ThemeColors?): AppColors { val baseColors = when (base) { DefaultTheme.LIGHT -> LightColorPaletteApp DefaultTheme.DARK -> DarkColorPaletteApp DefaultTheme.SIMPLEX -> SimplexColorPaletteApp - // shouldn't be here - DefaultTheme.SYSTEM -> LightColorPaletteApp + DefaultTheme.BLACK -> BlackColorPaletteApp } + + val sentMessageFallback = colors.sentMessage?.colorFromReadableHex() ?: presetWallpaperTheme?.sentMessage?.colorFromReadableHex() ?: baseColors.sentMessage + val sentQuoteFallback = colors.sentQuote?.colorFromReadableHex() ?: presetWallpaperTheme?.sentQuote?.colorFromReadableHex() ?: baseColors.sentQuote + val receivedMessageFallback = colors.receivedMessage?.colorFromReadableHex() ?: presetWallpaperTheme?.receivedMessage?.colorFromReadableHex() ?: baseColors.receivedMessage + val receivedQuoteFallback = colors.receivedQuote?.colorFromReadableHex() ?: presetWallpaperTheme?.receivedQuote?.colorFromReadableHex() ?: baseColors.receivedQuote return baseColors.copy( - title = title?.colorFromReadableHex() ?: baseColors.title, - sentMessage = sentMessage?.colorFromReadableHex() ?: baseColors.sentMessage, - receivedMessage = receivedMessage?.colorFromReadableHex() ?: baseColors.receivedMessage, + title = perChatTheme?.title?.colorFromReadableHex() ?: perUserTheme?.title?.colorFromReadableHex() ?: colors.title?.colorFromReadableHex() ?: presetWallpaperTheme?.title?.colorFromReadableHex() ?: baseColors.title, + primaryVariant2 = perChatTheme?.primaryVariant2?.colorFromReadableHex() ?: perUserTheme?.primaryVariant2?.colorFromReadableHex() ?: colors.primaryVariant2?.colorFromReadableHex() ?: presetWallpaperTheme?.primaryVariant2?.colorFromReadableHex() ?: baseColors.primaryVariant2, + sentMessage = if (perChatTheme?.sentMessage != null) perChatTheme.sentMessage.colorFromReadableHex() + else if (perUserTheme != null && (perChatWallpaperType == null || perUserWallpaperType == null || perChatWallpaperType.sameType(perUserWallpaperType))) perUserTheme.sentMessage?.colorFromReadableHex() ?: sentMessageFallback + else sentMessageFallback, + sentQuote = if (perChatTheme?.sentQuote != null) perChatTheme.sentQuote.colorFromReadableHex() + else if (perUserTheme != null && (perChatWallpaperType == null || perUserWallpaperType == null || perChatWallpaperType.sameType(perUserWallpaperType))) perUserTheme.sentQuote?.colorFromReadableHex() ?: sentQuoteFallback + else sentQuoteFallback, + receivedMessage = if (perChatTheme?.receivedMessage != null) perChatTheme.receivedMessage.colorFromReadableHex() + else if (perUserTheme != null && (perChatWallpaperType == null || perUserWallpaperType == null || perChatWallpaperType.sameType(perUserWallpaperType))) perUserTheme.receivedMessage?.colorFromReadableHex() ?: receivedMessageFallback + else receivedMessageFallback, + receivedQuote = if (perChatTheme?.receivedQuote != null) perChatTheme.receivedQuote.colorFromReadableHex() + else if (perUserTheme != null && (perChatWallpaperType == null || perUserWallpaperType == null || perChatWallpaperType.sameType(perUserWallpaperType))) perUserTheme.receivedQuote?.colorFromReadableHex() ?: receivedQuoteFallback + else receivedQuoteFallback, ) } - fun withFilledColors(base: DefaultTheme): ThemeColors { - val c = toColors(base) - val ac = toAppColors(base) + fun toAppWallpaper(themeOverridesForType: WallpaperType?, perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverride?, materialBackgroundColor: Color): AppWallpaper { + val mainType = when { + themeOverridesForType != null -> themeOverridesForType + // type can be null if override is empty `"wallpaper": "{}"`, in this case no wallpaper is needed, empty. + // It's not null to override upper level wallpaper + perChatTheme?.wallpaper != null -> perChatTheme.wallpaper.toAppWallpaper().type + perUserTheme?.wallpaper != null -> perUserTheme.wallpaper.toAppWallpaper().type + else -> wallpaper?.toAppWallpaper()?.type ?: return AppWallpaper() + } + val first: ThemeWallpaper? = if (mainType.sameType(perChatTheme?.wallpaper?.toAppWallpaper()?.type)) perChatTheme?.wallpaper else null + val second: ThemeWallpaper? = if (mainType.sameType(perUserTheme?.wallpaper?.toAppWallpaper()?.type)) perUserTheme?.wallpaper else null + val third: ThemeWallpaper? = if (mainType.sameType(this.wallpaper?.toAppWallpaper()?.type)) this.wallpaper else null + + return AppWallpaper(type = when (mainType) { + is WallpaperType.Preset -> mainType.copy( + scale = mainType.scale ?: first?.scale ?: second?.scale ?: third?.scale + ) + is WallpaperType.Image -> mainType.copy( + scale = if (themeOverridesForType == null) mainType.scale ?: first?.scale ?: second?.scale ?: third?.scale else second?.scale ?: third?.scale ?: mainType.scale, + scaleType = if (themeOverridesForType == null) mainType.scaleType ?: first?.scaleType ?: second?.scaleType ?: third?.scaleType else second?.scaleType ?: third?.scaleType ?: mainType.scaleType, + filename = if (themeOverridesForType == null) mainType.filename else first?.imageFile ?: second?.imageFile ?: third?.imageFile ?: mainType.filename, + ) + is WallpaperType.Empty -> mainType + }, + background = (first?.background ?: second?.background ?: third?.background)?.colorFromReadableHex() ?: mainType.defaultBackgroundColor(base, materialBackgroundColor), + tint = (first?.tint ?: second?.tint ?: third?.tint)?.colorFromReadableHex() ?: mainType.defaultTintColor(base) + ) + } + + fun withFilledColors(base: DefaultTheme, perChatTheme: ThemeColors?, perChatWallpaperType: WallpaperType?, perUserTheme: ThemeColors?, perUserWallpaperType: WallpaperType?, presetWallpaperTheme: ThemeColors?): ThemeColors { + val c = toColors(base, perChatTheme, perUserTheme, presetWallpaperTheme) + val ac = toAppColors(base, perChatTheme, perChatWallpaperType, perUserTheme, perUserWallpaperType, presetWallpaperTheme) return ThemeColors( primary = c.primary.toReadableHex(), primaryVariant = c.primaryVariant.toReadableHex(), @@ -139,23 +419,71 @@ data class ThemeColors( background = c.background.toReadableHex(), surface = c.surface.toReadableHex(), title = ac.title.toReadableHex(), + primaryVariant2 = ac.primaryVariant2.toReadableHex(), sentMessage = ac.sentMessage.toReadableHex(), - receivedMessage = ac.receivedMessage.toReadableHex() + sentQuote = ac.sentQuote.toReadableHex(), + receivedMessage = ac.receivedMessage.toReadableHex(), + receivedQuote = ac.receivedQuote.toReadableHex(), ) } } -private fun String.colorFromReadableHex(): Color = - Color(this.replace("#", "").toLongOrNull(16) ?: Color.White.toArgb().toLong()) +fun List.getTheme(themeId: String?): ThemeOverrides? = + firstOrNull { it.themeId == themeId } -private fun Color.toReadableHex(): String = "#" + Integer.toHexString(toArgb()) +fun List.getTheme(themeId: String?, type: WallpaperType?, base: DefaultTheme): ThemeOverrides? = + firstOrNull { it.themeId == themeId || it.isSame(type, base.themeName)} + +fun List.replace(theme: ThemeOverrides): List { + val index = indexOfFirst { it.themeId == theme.themeId || + // prevent situation when two themes has the same type but different theme id (maybe something was changed in prefs by hand) + it.isSame(WallpaperType.from(theme.wallpaper), theme.base.themeName) + } + return if (index != -1) { + val a = ArrayList(this) + a[index] = theme + a + } else { + this + theme + } +} + +fun List.sameTheme(type: WallpaperType?, themeName: String): ThemeOverrides? = firstOrNull { it.isSame(type, themeName) } + +/** See [ThemesTest.testSkipDuplicates] */ +fun List.skipDuplicates(): List { + val res = ArrayList() + forEach { theme -> + val themeType = WallpaperType.from(theme.wallpaper) + if (res.none { it.themeId == theme.themeId || it.isSame(themeType, theme.base.themeName) }) { + res.add(theme) + } + } + return res +} @Serializable -data class ThemeOverrides ( - val base: DefaultTheme, - val colors: ThemeColors +data class ThemeModeOverrides ( + val light: ThemeModeOverride? = null, + val dark: ThemeModeOverride? = null ) { - fun withUpdatedColor(name: ThemeColor, color: String): ThemeOverrides { + fun preferredMode(darkTheme: Boolean): ThemeModeOverride? = when (darkTheme) { + false -> light + else -> dark + } +} + +@Serializable +data class ThemeModeOverride ( + val mode: DefaultThemeMode = CurrentColors.value.base.mode, + val colors: ThemeColors = ThemeColors(), + val wallpaper: ThemeWallpaper? = null, +) { + + @Transient + val type = WallpaperType.from(wallpaper) + + fun withUpdatedColor(name: ThemeColor, color: String?): ThemeModeOverride { return copy(colors = when (name) { ThemeColor.PRIMARY -> colors.copy(primary = color) ThemeColor.PRIMARY_VARIANT -> colors.copy(primaryVariant = color) @@ -164,9 +492,97 @@ data class ThemeOverrides ( ThemeColor.BACKGROUND -> colors.copy(background = color) ThemeColor.SURFACE -> colors.copy(surface = color) ThemeColor.TITLE -> colors.copy(title = color) + ThemeColor.PRIMARY_VARIANT2 -> colors.copy(primaryVariant2 = color) ThemeColor.SENT_MESSAGE -> colors.copy(sentMessage = color) + ThemeColor.SENT_QUOTE -> colors.copy(sentQuote = color) ThemeColor.RECEIVED_MESSAGE -> colors.copy(receivedMessage = color) - }) + ThemeColor.RECEIVED_QUOTE -> colors.copy(receivedQuote = color) + ThemeColor.WALLPAPER_BACKGROUND -> colors.copy() + ThemeColor.WALLPAPER_TINT -> colors.copy() + }, wallpaper = when (name) { + ThemeColor.WALLPAPER_BACKGROUND -> wallpaper?.copy(background = color) + ThemeColor.WALLPAPER_TINT -> wallpaper?.copy(tint = color) + else -> wallpaper?.copy() + } + ) + } + + fun removeSameColors(base: DefaultTheme): ThemeModeOverride { + val c = when (base) { + DefaultTheme.LIGHT -> LightColorPalette + DefaultTheme.DARK -> DarkColorPalette + DefaultTheme.SIMPLEX -> SimplexColorPalette + DefaultTheme.BLACK -> BlackColorPalette + } + val ac = when (base) { + DefaultTheme.LIGHT -> LightColorPaletteApp + DefaultTheme.DARK -> DarkColorPaletteApp + DefaultTheme.SIMPLEX -> SimplexColorPaletteApp + DefaultTheme.BLACK -> BlackColorPaletteApp + } + val w = when (val wallpaperType = WallpaperType.from(wallpaper)) { + is WallpaperType.Preset -> { + val p = PresetWallpaper.from(wallpaperType.filename) + ThemeWallpaper( + preset = wallpaperType.filename, + scale = p?.scale ?: wallpaper?.scale, + scaleType = null, + background = p?.background?.get(base)?.toReadableHex(), + tint = p?.tint?.get(base)?.toReadableHex(), + image = null, + imageFile = null, + ) + } + is WallpaperType.Image -> { + ThemeWallpaper( + preset = null, + scale = null, + scaleType = WallpaperScaleType.FILL, + background = Color.Transparent.toReadableHex(), + tint = Color.Transparent.toReadableHex(), + image = null, + imageFile = null, + ) + } + else -> { + ThemeWallpaper() + } + } + + return copy( + colors = ThemeColors( + primary = if (colors.primary?.colorFromReadableHex() != c.primary) colors.primary else null, + primaryVariant = if (colors.primaryVariant?.colorFromReadableHex() != c.primaryVariant) colors.primaryVariant else null, + secondary = if (colors.secondary?.colorFromReadableHex() != c.secondary) colors.secondary else null, + secondaryVariant = if (colors.secondaryVariant?.colorFromReadableHex() != c.secondaryVariant) colors.secondaryVariant else null, + background = if (colors.background?.colorFromReadableHex() != c.background) colors.background else null, + surface = if (colors.surface?.colorFromReadableHex() != c.surface) colors.surface else null, + title = if (colors.title?.colorFromReadableHex() != ac.title) colors.title else null, + primaryVariant2 = if (colors.primaryVariant2?.colorFromReadableHex() != ac.primaryVariant2) colors.primary else null, + sentMessage = if (colors.sentMessage?.colorFromReadableHex() != ac.sentMessage) colors.sentMessage else null, + sentQuote = if (colors.sentQuote?.colorFromReadableHex() != ac.sentQuote) colors.sentQuote else null, + receivedMessage = if (colors.receivedMessage?.colorFromReadableHex() != ac.receivedMessage) colors.receivedMessage else null, + receivedQuote = if (colors.receivedQuote?.colorFromReadableHex() != ac.receivedQuote) colors.receivedQuote else null, + ), + wallpaper = wallpaper?.copy( + preset = wallpaper.preset, + scale = if (wallpaper.scale != w.scale) wallpaper.scale else null, + scaleType = if (wallpaper.scaleType != w.scaleType) wallpaper.scaleType else null, + background = if (wallpaper.background != w.background) wallpaper.background else null, + tint = if (wallpaper.tint != w.tint) wallpaper.tint else null, + image = wallpaper.image, + imageFile = wallpaper.imageFile, + ) + ) + } + + companion object { + fun withFilledAppDefaults(mode: DefaultThemeMode, base: DefaultTheme): ThemeModeOverride = + ThemeModeOverride( + mode = mode, + colors = ThemeOverrides(base = base).withFilledColors(base, null, null, null, null, null), + wallpaper = ThemeWallpaper(preset = PresetWallpaper.SCHOOL.filename) + ) } } @@ -204,7 +620,6 @@ val DarkColorPalette = darkColors( // background = Color.Black, surface = Color(0xFF222222), // background = Color(0xFF121212), -// surface = Color(0xFF121212), error = Color.Red, onBackground = Color(0xFFFFFBFA), onSurface = Color(0xFFFFFBFA), @@ -212,8 +627,11 @@ val DarkColorPalette = darkColors( ) val DarkColorPaletteApp = AppColors( title = SimplexBlue, - sentMessage = SentMessageColor, - receivedMessage = Color(0x20B1B0B5) + primaryVariant2 = Color(0xFF18262E), + sentMessage = Color(0xFF18262E), + sentQuote = Color(0xFF1D3847), + receivedMessage = Color(0xff262627), + receivedQuote = Color(0xff373739), ) val LightColorPalette = lightColors( @@ -231,8 +649,11 @@ val LightColorPalette = lightColors( ) val LightColorPaletteApp = AppColors( title = SimplexBlue, - sentMessage = SentMessageColor, - receivedMessage = Color(0x20B1B0B5) + primaryVariant2 = Color(0xFFE9F7FF), + sentMessage = Color(0xFFE9F7FF), + sentQuote = Color(0xFFD6F0FF), + receivedMessage = Color(0xfff5f5f6), + receivedQuote = Color(0xffececee), ) val SimplexColorPalette = darkColors( @@ -251,11 +672,39 @@ val SimplexColorPalette = darkColors( ) val SimplexColorPaletteApp = AppColors( title = Color(0xFF267BE5), - sentMessage = SentMessageColor, - receivedMessage = Color(0x20B1B0B5) + primaryVariant2 = Color(0xFF172941), + sentMessage = Color(0xFF172941), + sentQuote = Color(0xFF1C3A57), + receivedMessage = Color(0xff25283a), + receivedQuote = Color(0xff36394a), ) -val CurrentColors: MutableStateFlow = MutableStateFlow(ThemeManager.currentColors(isInNightMode())) +val BlackColorPalette = darkColors( + primary = Color(0xff0077e0), // If this value changes also need to update #0088ff in string resource files + primaryVariant = Color(0xff0077e0), + secondary = HighOrLowlight, + secondaryVariant = DarkGray, + background = Color(0xff070707), + surface = Color(0xff161617), + // background = Color(0xFF121212), + // surface = Color(0xFF121212), + error = Color.Red, + onBackground = Color(0xFFFFFBFA), + onSurface = Color(0xFFFFFBFA), + // onError: Color = Color.Black, +) +val BlackColorPaletteApp = AppColors( + title = Color(0xff0077e0), + primaryVariant2 = Color(0xff243747), + sentMessage = Color(0xFF18262E), + sentQuote = Color(0xFF1D3847), + receivedMessage = Color(0xff1b1b1b), + receivedQuote = Color(0xff29292b), +) + +var systemInDarkThemeCurrently: Boolean = isInNightMode() + +val CurrentColors: MutableStateFlow = MutableStateFlow(ThemeManager.currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get())) @Composable fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLight @@ -263,31 +712,113 @@ fun isInDarkTheme(): Boolean = !CurrentColors.collectAsState().value.colors.isLi @Composable expect fun isSystemInDarkTheme(): Boolean +internal val LocalAppColors = staticCompositionLocalOf { LightColorPaletteApp } +internal val LocalAppWallpaper = staticCompositionLocalOf { AppWallpaper() } + +val MaterialTheme.appColors: AppColors + @Composable + @ReadOnlyComposable + get() = LocalAppColors.current + +fun AppColors.updateColorsFrom(other: AppColors) { + title = other.title + primaryVariant2 = other.primaryVariant2 + sentMessage = other.sentMessage + sentQuote = other.sentQuote + receivedMessage = other.receivedMessage + receivedQuote = other.receivedQuote +} + +fun AppWallpaper.updateWallpaperFrom(other: AppWallpaper) { + background = other.background + tint = other.tint + type = other.type +} + +val MaterialTheme.wallpaper: AppWallpaper + @Composable + @ReadOnlyComposable + get() = LocalAppWallpaper.current + fun reactOnDarkThemeChanges(isDark: Boolean) { - if (ChatController.appPrefs.currentTheme.get() == DefaultTheme.SYSTEM.name && CurrentColors.value.colors.isLight == isDark) { + systemInDarkThemeCurrently = isDark + if (ChatController.appPrefs.currentTheme.get() == DefaultTheme.SYSTEM_THEME_NAME && CurrentColors.value.colors.isLight == isDark) { // Change active colors from light to dark and back based on system theme - ThemeManager.applyTheme(DefaultTheme.SYSTEM.name, isDark) + ThemeManager.applyTheme(DefaultTheme.SYSTEM_THEME_NAME) } } @Composable fun SimpleXTheme(darkTheme: Boolean? = null, content: @Composable () -> Unit) { - LaunchedEffect(darkTheme) { - // For preview - if (darkTheme != null) - CurrentColors.value = ThemeManager.currentColors(darkTheme) - } - val systemDark = isSystemInDarkTheme() - LaunchedEffect(systemDark) { - reactOnDarkThemeChanges(systemDark) +// TODO: Fix preview working with dark/light theme + +// LaunchedEffect(darkTheme) { +// // For preview +// if (darkTheme != null) +// CurrentColors.value = ThemeManager.currentColors(darkTheme, null, null, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get()) +// } + val systemDark = rememberUpdatedState(isSystemInDarkTheme()) + LaunchedEffect(Unit) { + // snapshotFlow vs LaunchedEffect reduce number of recomposes + snapshotFlow { systemDark.value } + .collect { + reactOnDarkThemeChanges(systemDark.value) + } } val theme by CurrentColors.collectAsState() + LaunchedEffect(Unit) { + // snapshotFlow vs LaunchedEffect reduce number of recomposes when user is changed or it's themes + snapshotFlow { chatModel.currentUser.value?.uiThemes } + .collect { + ThemeManager.applyTheme(appPrefs.currentTheme.get()!!) + } + } MaterialTheme( colors = theme.colors, typography = Typography, shapes = Shapes, content = { - CompositionLocalProvider(LocalContentColor provides MaterialTheme.colors.onBackground, content = content) + val rememberedAppColors = remember { + // Explicitly creating a new object here so we don't mutate the initial [appColors] + // provided, and overwrite the values set in it. + theme.appColors.copy() + }.apply { updateColorsFrom(theme.appColors) } + val rememberedWallpaper = remember { + // Explicitly creating a new object here so we don't mutate the initial [wallpaper] + // provided, and overwrite the values set in it. + theme.wallpaper.copy() + }.apply { updateWallpaperFrom(theme.wallpaper) } + CompositionLocalProvider( + LocalContentColor provides MaterialTheme.colors.onBackground, + LocalAppColors provides rememberedAppColors, + LocalAppWallpaper provides rememberedWallpaper, + content = content) + } + ) +} + +@Composable +fun SimpleXThemeOverride(theme: ThemeManager.ActiveTheme, content: @Composable () -> Unit) { + MaterialTheme( + colors = theme.colors, + typography = Typography, + shapes = Shapes, + content = { + val rememberedAppColors = remember { + // Explicitly creating a new object here so we don't mutate the initial [appColors] + // provided, and overwrite the values set in it. + theme.appColors.copy() + }.apply { updateColorsFrom(theme.appColors) } + val rememberedWallpaper = remember { + // Explicitly creating a new object here so we don't mutate the initial [wallpaper] + // provided, and overwrite the values set in it. + theme.wallpaper.copy() + }.apply { updateWallpaperFrom(theme.wallpaper) } + CompositionLocalProvider( + LocalContentColor provides MaterialTheme.colors.onBackground, + LocalAppColors provides rememberedAppColors, + LocalAppWallpaper provides rememberedWallpaper, + content = content) } ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt index 49d3203455..2f8f6ed0bf 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/ui/theme/ThemeManager.kt @@ -1,14 +1,14 @@ package chat.simplex.common.ui.theme import androidx.compose.material.Colors +import androidx.compose.runtime.MutableState import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.toArgb import androidx.compose.ui.text.font.FontFamily -import chat.simplex.res.MR -import chat.simplex.common.model.AppPreferences -import chat.simplex.common.model.ChatController -import chat.simplex.common.platform.platform -import chat.simplex.common.views.helpers.generalGetString +import chat.simplex.common.model.* +import chat.simplex.common.platform.* +import chat.simplex.common.views.helpers.* +import java.io.File // https://github.com/rsms/inter // I place it here because IDEA shows an error (but still works anyway) when this declaration inside Type.kt @@ -18,140 +18,215 @@ expect val EmojiFont: FontFamily object ThemeManager { private val appPrefs: AppPreferences = ChatController.appPrefs - data class ActiveTheme(val name: String, val base: DefaultTheme, val colors: Colors, val appColors: AppColors) + data class ActiveTheme(val name: String, val base: DefaultTheme, val colors: Colors, val appColors: AppColors, val wallpaper: AppWallpaper = AppWallpaper()) private fun systemDarkThemeColors(): Pair = when (appPrefs.systemDarkTheme.get()) { - DefaultTheme.DARK.name -> DarkColorPalette to DefaultTheme.DARK - DefaultTheme.SIMPLEX.name -> SimplexColorPalette to DefaultTheme.SIMPLEX + DefaultTheme.DARK.themeName -> DarkColorPalette to DefaultTheme.DARK + DefaultTheme.SIMPLEX.themeName -> SimplexColorPalette to DefaultTheme.SIMPLEX + DefaultTheme.BLACK.themeName -> BlackColorPalette to DefaultTheme.BLACK else -> SimplexColorPalette to DefaultTheme.SIMPLEX } - fun currentColors(darkForSystemTheme: Boolean): ActiveTheme { + private fun nonSystemThemeName(): String { val themeName = appPrefs.currentTheme.get()!! - val themeOverrides = appPrefs.themeOverrides.get() - - val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) { + return if (themeName != DefaultTheme.SYSTEM_THEME_NAME) { themeName } else { - if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name + if (systemInDarkThemeCurrently) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.themeName } - val theme = themeOverrides[nonSystemThemeName] + } + + fun defaultActiveTheme(appSettingsTheme: List): ThemeOverrides? { + val nonSystemThemeName = nonSystemThemeName() + val defaultThemeId = appPrefs.currentThemeIds.get()[nonSystemThemeName] + return appSettingsTheme.getTheme(defaultThemeId) + } + + fun defaultActiveTheme(perUserTheme: ThemeModeOverrides?, appSettingsTheme: List): ThemeModeOverride { + val perUserTheme = if (!CurrentColors.value.colors.isLight) perUserTheme?.dark else perUserTheme?.light + if (perUserTheme != null) { + return perUserTheme + } + val defaultTheme = defaultActiveTheme(appSettingsTheme) + return ThemeModeOverride(colors = defaultTheme?.colors ?: ThemeColors(), wallpaper = defaultTheme?.wallpaper) + } + + fun currentColors(themeOverridesForType: WallpaperType?, perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverrides?, appSettingsTheme: List): ActiveTheme { + val themeName = appPrefs.currentTheme.get()!! + val nonSystemThemeName = nonSystemThemeName() + val defaultTheme = defaultActiveTheme(appSettingsTheme) + val baseTheme = when (nonSystemThemeName) { - DefaultTheme.LIGHT.name -> Triple(DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp) - DefaultTheme.DARK.name -> Triple(DefaultTheme.DARK, DarkColorPalette, DarkColorPaletteApp) - DefaultTheme.SIMPLEX.name -> Triple(DefaultTheme.SIMPLEX, SimplexColorPalette, SimplexColorPaletteApp) - else -> Triple(DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp) + DefaultTheme.LIGHT.themeName -> ActiveTheme(DefaultTheme.LIGHT.themeName, DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.LIGHT))) + DefaultTheme.DARK.themeName -> ActiveTheme(DefaultTheme.DARK.themeName, DefaultTheme.DARK, DarkColorPalette, DarkColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.DARK))) + DefaultTheme.SIMPLEX.themeName -> ActiveTheme(DefaultTheme.SIMPLEX.themeName, DefaultTheme.SIMPLEX, SimplexColorPalette, SimplexColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.SIMPLEX))) + DefaultTheme.BLACK.themeName -> ActiveTheme(DefaultTheme.BLACK.themeName, DefaultTheme.BLACK, BlackColorPalette, BlackColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.BLACK))) + else -> ActiveTheme(DefaultTheme.LIGHT.themeName, DefaultTheme.LIGHT, LightColorPalette, LightColorPaletteApp, AppWallpaper(type = PresetWallpaper.SCHOOL.toType(DefaultTheme.LIGHT))) } - if (theme == null) { - return ActiveTheme(themeName, baseTheme.first, baseTheme.second, baseTheme.third) + + val perUserTheme = if (baseTheme.colors.isLight) perUserTheme?.light else perUserTheme?.dark + val theme = (appSettingsTheme.sameTheme(themeOverridesForType ?: perChatTheme?.type ?: perUserTheme?.type ?: defaultTheme?.wallpaper?.toAppWallpaper()?.type, nonSystemThemeName) ?: defaultTheme) + + if (theme == null && perUserTheme == null && perChatTheme == null && themeOverridesForType == null) { + return ActiveTheme(themeName, baseTheme.base, baseTheme.colors, baseTheme.appColors, baseTheme.wallpaper) } - return ActiveTheme(themeName, baseTheme.first, theme.colors.toColors(theme.base), theme.colors.toAppColors(theme.base)) + val presetWallpaperTheme = when { + perChatTheme?.wallpaper != null -> if (perChatTheme.wallpaper.preset != null) PresetWallpaper.from(perChatTheme.wallpaper.preset)?.colors?.get(baseTheme.base) else null + perUserTheme?.wallpaper != null -> if (perUserTheme.wallpaper.preset != null) PresetWallpaper.from(perUserTheme.wallpaper.preset)?.colors?.get(baseTheme.base) else null + else -> if (theme?.wallpaper?.preset != null) PresetWallpaper.from(theme.wallpaper.preset)?.colors?.get(baseTheme.base) else null + } + val themeOrEmpty = theme ?: ThemeOverrides(base = baseTheme.base) + val colors = themeOrEmpty.toColors(themeOrEmpty.base, perChatTheme?.colors, perUserTheme?.colors, presetWallpaperTheme) + return ActiveTheme( + themeName, + baseTheme.base, + colors, + themeOrEmpty.toAppColors(themeOrEmpty.base, perChatTheme?.colors, perChatTheme?.type, perUserTheme?.colors, perUserTheme?.type, presetWallpaperTheme), + themeOrEmpty.toAppWallpaper(themeOverridesForType, perChatTheme, perUserTheme, colors.background) + ) } - fun currentThemeOverridesForExport(darkForSystemTheme: Boolean): ThemeOverrides { - val themeName = appPrefs.currentTheme.get()!! - val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) { - themeName - } else { - if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name - } - val overrides = appPrefs.themeOverrides.get().toMutableMap() - val nonFilledTheme = overrides[nonSystemThemeName] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors()) - return nonFilledTheme.copy(colors = nonFilledTheme.colors.withFilledColors(CurrentColors.value.base)) + fun currentThemeOverridesForExport(perChatTheme: ThemeModeOverride?, perUserTheme: ThemeModeOverrides?): ThemeOverrides { + val current = currentColors(null, perChatTheme, perUserTheme, appPrefs.themeOverrides.get()) + val wType = current.wallpaper.type + val wBackground = current.wallpaper.background + val wTint = current.wallpaper.tint + return ThemeOverrides( + themeId = "", + base = current.base, + colors = ThemeColors.from(current.colors, current.appColors), + wallpaper = if (wType !is WallpaperType.Empty) ThemeWallpaper.from(wType, wBackground?.toReadableHex(), wTint?.toReadableHex()).withFilledWallpaperBase64() else null + ) } - // colors, default theme enum, localized name of theme - fun allThemes(darkForSystemTheme: Boolean): List> { - val allThemes = ArrayList>() - allThemes.add( - Triple( - if (darkForSystemTheme) systemDarkThemeColors().first else LightColorPalette, - DefaultTheme.SYSTEM, - generalGetString(MR.strings.theme_system) - ) - ) - allThemes.add( - Triple( - LightColorPalette, - DefaultTheme.LIGHT, - generalGetString(MR.strings.theme_light) - ) - ) - allThemes.add( - Triple( - DarkColorPalette, - DefaultTheme.DARK, - generalGetString(MR.strings.theme_dark) - ) - ) - allThemes.add( - Triple( - SimplexColorPalette, - DefaultTheme.SIMPLEX, - generalGetString(MR.strings.theme_simplex) - ) - ) - return allThemes - } - - fun applyTheme(theme: String, darkForSystemTheme: Boolean) { + fun applyTheme(theme: String) { appPrefs.currentTheme.set(theme) - CurrentColors.value = currentColors(darkForSystemTheme) + CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) platform.androidSetNightModeIfSupported() } - fun changeDarkTheme(theme: String, darkForSystemTheme: Boolean) { + fun changeDarkTheme(theme: String) { appPrefs.systemDarkTheme.set(theme) - CurrentColors.value = currentColors(darkForSystemTheme) + CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) } - fun saveAndApplyThemeColor(name: ThemeColor, color: Color? = null, darkForSystemTheme: Boolean) { - val themeName = appPrefs.currentTheme.get()!! - val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) { - themeName - } else { - if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name + fun saveAndApplyThemeColor(baseTheme: DefaultTheme, name: ThemeColor, color: Color? = null, pref: SharedPreference> = appPrefs.themeOverrides) { + val nonSystemThemeName = baseTheme.themeName + val overrides = pref.get() + val themeId = appPrefs.currentThemeIds.get()[nonSystemThemeName] + val prevValue = overrides.getTheme(themeId) ?: ThemeOverrides(base = baseTheme) + pref.set(overrides.replace(prevValue.withUpdatedColor(name, color?.toReadableHex()))) + val themeIds = appPrefs.currentThemeIds.get().toMutableMap() + themeIds[nonSystemThemeName] = prevValue.themeId + appPrefs.currentThemeIds.set(themeIds) + CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) + } + + fun applyThemeColor(name: ThemeColor, color: Color? = null, pref: MutableState) { + pref.value = pref.value.withUpdatedColor(name, color?.toReadableHex()) + } + + fun saveAndApplyWallpaper(baseTheme: DefaultTheme, type: WallpaperType?, pref: SharedPreference> = appPrefs.themeOverrides) { + val nonSystemThemeName = baseTheme.themeName + val overrides = pref.get() + val theme = overrides.sameTheme(type, baseTheme.themeName) + val prevValue = theme ?: ThemeOverrides(base = baseTheme) + pref.set(overrides.replace(prevValue.copy(wallpaper = if (type != null && type !is WallpaperType.Empty) ThemeWallpaper.from(type, prevValue.wallpaper?.background, prevValue.wallpaper?.tint) else null))) + val themeIds = appPrefs.currentThemeIds.get().toMutableMap() + themeIds[nonSystemThemeName] = prevValue.themeId + appPrefs.currentThemeIds.set(themeIds) + CurrentColors.value = currentColors( null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) + } + + fun copyFromSameThemeOverrides(type: WallpaperType?, lowerLevelOverride: ThemeModeOverride?, pref: MutableState): Boolean { + val overrides = appPrefs.themeOverrides.get() + val sameWallpaper = if (lowerLevelOverride?.type?.sameType(type) == true) lowerLevelOverride.wallpaper else overrides.sameTheme(type, CurrentColors.value.base.themeName)?.wallpaper + if (sameWallpaper == null) { + if (type != null) { + pref.value = ThemeModeOverride(wallpaper = ThemeWallpaper.from(type, null, null).copy(scale = null, scaleType = null)) + } else { + // Make an empty wallpaper to override any top level ones + pref.value = ThemeModeOverride(wallpaper = ThemeWallpaper()) + } + return true } - var colorToSet = color - if (colorToSet == null) { - // Setting default color from a base theme - colorToSet = when(nonSystemThemeName) { - DefaultTheme.LIGHT.name -> name.fromColors(LightColorPalette, LightColorPaletteApp) - DefaultTheme.DARK.name -> name.fromColors(DarkColorPalette, DarkColorPaletteApp) - DefaultTheme.SIMPLEX.name -> name.fromColors(SimplexColorPalette, SimplexColorPaletteApp) - // Will not be here - else -> return + var type = sameWallpaper.toAppWallpaper().type + if (type is WallpaperType.Image && sameWallpaper.imageFile == type.filename) { + // same image file. Needs to be copied first in order to be able to remove the file once it's not needed anymore without affecting main theme override + val filename = saveWallpaperFile(File(getWallpaperFilePath(type.filename)).toURI()) + if (filename != null) { + type = WallpaperType.Image(filename, type.scale, type.scaleType) + } else { + Log.e(TAG, "Error while copying wallpaper from global overrides to chat overrides") + return false } } - val overrides = appPrefs.themeOverrides.get().toMutableMap() - val prevValue = overrides[nonSystemThemeName] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors()) - overrides[nonSystemThemeName] = prevValue.withUpdatedColor(name, colorToSet.toReadableHex()) - appPrefs.themeOverrides.set(overrides) - CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight) + val prevValue = pref.value + pref.value = prevValue.copy( + colors = ThemeColors(), + wallpaper = ThemeWallpaper.from(type, null, null).copy(scale = null, scaleType = null) + ) + return true } - fun saveAndApplyThemeOverrides(theme: ThemeOverrides, darkForSystemTheme: Boolean) { - val overrides = appPrefs.themeOverrides.get().toMutableMap() - val prevValue = overrides[theme.base.name] ?: ThemeOverrides(base = CurrentColors.value.base, colors = ThemeColors()) - overrides[theme.base.name] = prevValue.copy(colors = theme.colors) - appPrefs.themeOverrides.set(overrides) - appPrefs.currentTheme.set(theme.base.name) - CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight) + fun applyWallpaper(type: WallpaperType?, pref: MutableState) { + val prevValue = pref.value + pref.value = prevValue.copy( + wallpaper = if (type != null) + ThemeWallpaper.from(type, prevValue.wallpaper?.background, prevValue.wallpaper?.tint) + else null + ) } - fun resetAllThemeColors(darkForSystemTheme: Boolean) { - val themeName = appPrefs.currentTheme.get()!! - val nonSystemThemeName = if (themeName != DefaultTheme.SYSTEM.name) { - themeName - } else { - if (darkForSystemTheme) appPrefs.systemDarkTheme.get()!! else DefaultTheme.LIGHT.name + fun saveAndApplyThemeOverrides(theme: ThemeOverrides, pref: SharedPreference> = appPrefs.themeOverrides) { + val wallpaper = theme.wallpaper?.importFromString() + val nonSystemThemeName = theme.base.themeName + val overrides = pref.get() + val prevValue = overrides.getTheme(null, wallpaper?.toAppWallpaper()?.type, theme.base) ?: ThemeOverrides(base = theme.base) + if (prevValue.wallpaper?.imageFile != null) { + File(getWallpaperFilePath(prevValue.wallpaper.imageFile)).delete() + } + pref.set(overrides.replace(prevValue.copy(base = theme.base, colors = theme.colors, wallpaper = wallpaper))) + appPrefs.currentTheme.set(nonSystemThemeName) + val currentThemeIds = appPrefs.currentThemeIds.get().toMutableMap() + currentThemeIds[nonSystemThemeName] = prevValue.themeId + appPrefs.currentThemeIds.set(currentThemeIds) + CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) + } + + fun resetAllThemeColors(pref: SharedPreference> = appPrefs.themeOverrides) { + val nonSystemThemeName = nonSystemThemeName() + val themeId = appPrefs.currentThemeIds.get()[nonSystemThemeName] ?: return + val overrides = pref.get() + val prevValue = overrides.getTheme(themeId) ?: return + pref.set(overrides.replace(prevValue.copy(colors = ThemeColors(), wallpaper = prevValue.wallpaper?.copy(background = null, tint = null)))) + CurrentColors.value = currentColors(null, null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) + } + + fun resetAllThemeColors(pref: MutableState) { + val prevValue = pref.value + pref.value = prevValue.copy(colors = ThemeColors(), wallpaper = prevValue.wallpaper?.copy(background = null, tint = null)) + } + + fun removeTheme(themeId: String?) { + val themes = ArrayList(appPrefs.themeOverrides.get()) + themes.removeAll { it.themeId == themeId } + appPrefs.themeOverrides.set(themes) + } + + fun String.colorFromReadableHex(): Color = + Color(this.replace("#", "").toLongOrNull(16) ?: Color.White.toArgb().toLong()) + + fun Color.toReadableHex(): String { + val s = Integer.toHexString(toArgb()) + return when { + this == Color.Transparent -> "#00ffffff" + s.length == 1 -> "#ff$s$s$s$s$s$s" + s.length == 2 -> "#ff$s$s$s" + s.length == 3 -> "#ff$s$s" + s.length == 6 && this.alpha == 0f -> "#00$s" + s.length == 6 -> "#ff$s" + else -> "#$s" } - val overrides = appPrefs.themeOverrides.get().toMutableMap() - val prevValue = overrides[nonSystemThemeName] ?: return - overrides[nonSystemThemeName] = prevValue.copy(colors = ThemeColors()) - appPrefs.themeOverrides.set(overrides) - CurrentColors.value = currentColors(!CurrentColors.value.colors.isLight) } } - -private fun Color.toReadableHex(): String = "#" + Integer.toHexString(toArgb()) 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 dcd36e026b..48ed0570a7 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 @@ -29,6 +29,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* @@ -41,6 +42,8 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.flow.* import kotlinx.coroutines.launch import kotlinx.datetime.Clock +import kotlinx.serialization.encodeToString +import java.io.File @Composable fun ChatInfoView( @@ -337,6 +340,16 @@ fun ChatInfoLayout( if (cStats != null && cStats.ratchetSyncAllowed) { SynchronizeConnectionButton(syncContactConnection) } + + WallpaperButton { + ModalManager.end.showModal { + val chat = remember { derivedStateOf { chatModel.chats.firstOrNull { it.id == chat.id } } } + val c = chat.value + if (c != null) { + ChatWallpaperEditorModal(c) + } + } + } // } else if (developerTools) { // SynchronizeConnectionButtonForce(syncContactConnectionForce) // } @@ -406,6 +419,19 @@ fun ChatInfoLayout( SectionView(title = stringResource(MR.strings.section_title_for_console)) { InfoRow(stringResource(MR.strings.info_row_local_name), chat.chatInfo.localDisplayName) InfoRow(stringResource(MR.strings.info_row_database_id), chat.chatInfo.apiId.toString()) + SectionItemView({ + withBGApi { + val info = controller.apiContactQueueInfo(chat.remoteHostId, chat.chatInfo.apiId) + if (info != null) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.message_queue_info), + text = queueInfoText(info) + ) + } + } + }) { + Text(stringResource(MR.strings.info_row_debug_delivery)) + } } } SectionBottomSpacer() @@ -642,6 +668,15 @@ private fun SendReceiptsOption(currentUser: User, state: State, on ) } +@Composable +fun WallpaperButton(onClick: () -> Unit) { + SettingsActionItem( + painterResource(MR.images.ic_image), + stringResource(MR.strings.settings_section_title_chat_theme), + click = onClick + ) +} + @Composable fun ClearChatButton(onClick: () -> Unit) { SettingsActionItem( @@ -675,6 +710,72 @@ fun ShareAddressButton(onClick: () -> Unit) { ) } +@Composable +fun ModalData.ChatWallpaperEditorModal(chat: Chat) { + val themes = remember(CurrentColors.collectAsState().value.base) { + (chat.chatInfo as? ChatInfo.Direct)?.contact?.uiThemes + ?: (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.uiThemes + ?: ThemeModeOverrides() + } + val globalThemeUsed = remember { stateGetOrPut("globalThemeUsed") { false } } + val initialTheme = remember(CurrentColors.collectAsState().value.base) { + val preferred = themes.preferredMode(!CurrentColors.value.colors.isLight) + globalThemeUsed.value = preferred == null + preferred ?: ThemeManager.defaultActiveTheme(chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) + } + ChatWallpaperEditor( + initialTheme, + applyToMode = if (themes.light == themes.dark) null else initialTheme.mode, + globalThemeUsed = globalThemeUsed, + save = { applyToMode, newTheme -> + save(applyToMode, newTheme, chatModel.getChat(chat.id) ?: chat) + }) +} + +suspend fun save(applyToMode: DefaultThemeMode?, newTheme: ThemeModeOverride?, chat: Chat) { + val unchangedThemes: ThemeModeOverrides = ((chat.chatInfo as? ChatInfo.Direct)?.contact?.uiThemes ?: (chat.chatInfo as? ChatInfo.Group)?.groupInfo?.uiThemes) ?: ThemeModeOverrides() + val wallpaperFiles = setOf(unchangedThemes.light?.wallpaper?.imageFile, unchangedThemes.dark?.wallpaper?.imageFile) + var changedThemes: ThemeModeOverrides? = unchangedThemes + val changed = newTheme?.copy(wallpaper = newTheme.wallpaper?.withFilledWallpaperPath()) + changedThemes = when (applyToMode) { + null -> changedThemes?.copy(light = changed?.copy(mode = DefaultThemeMode.LIGHT), dark = changed?.copy(mode = DefaultThemeMode.DARK)) + DefaultThemeMode.LIGHT -> changedThemes?.copy(light = changed?.copy(mode = applyToMode)) + DefaultThemeMode.DARK -> changedThemes?.copy(dark = changed?.copy(mode = applyToMode)) + } + changedThemes = if (changedThemes?.light != null || changedThemes?.dark != null) { + val light = changedThemes.light + val dark = changedThemes.dark + val currentMode = CurrentColors.value.base.mode + // same image file for both modes, copy image to make them as different files + if (light?.wallpaper?.imageFile != null && dark?.wallpaper?.imageFile != null && light.wallpaper.imageFile == dark.wallpaper.imageFile) { + val imageFile = if (currentMode == DefaultThemeMode.LIGHT) { + dark.wallpaper.imageFile + } else { + light.wallpaper.imageFile + } + val filePath = saveWallpaperFile(File(getWallpaperFilePath(imageFile)).toURI()) + changedThemes = if (currentMode == DefaultThemeMode.LIGHT) { + changedThemes.copy(dark = dark.copy(wallpaper = dark.wallpaper.copy(imageFile = filePath))) + } else { + changedThemes.copy(light = light.copy(wallpaper = light.wallpaper.copy(imageFile = filePath))) + } + } + changedThemes + } else { + null + } + val wallpaperFilesToDelete = wallpaperFiles - changedThemes?.light?.wallpaper?.imageFile - changedThemes?.dark?.wallpaper?.imageFile + wallpaperFilesToDelete.forEach(::removeWallpaperFile) + + if (controller.apiSetChatUIThemes(chat.remoteHostId, chat.id, changedThemes)) { + if (chat.chatInfo is ChatInfo.Direct) { + chatModel.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(contact = chat.chatInfo.contact.copy(uiThemes = changedThemes))) + } else if (chat.chatInfo is ChatInfo.Group) { + chatModel.updateChatInfo(chat.remoteHostId, chat.chatInfo.copy(groupInfo = chat.chatInfo.groupInfo.copy(uiThemes = changedThemes))) + } + } +} + private fun setContactAlias(chat: Chat, localAlias: String, chatModel: ChatModel) = withBGApi { val chatRh = chat.remoteHostId chatModel.controller.apiSetContactAlias(chatRh, chat.chatInfo.apiId, localAlias)?.let { @@ -711,6 +812,12 @@ fun showSyncConnectionForceAlert(syncConnectionForce: () -> Unit) { ) } +fun queueInfoText(info: Pair): String { + val (rcvMsgInfo, qInfo) = info + val msgInfo: String = if (rcvMsgInfo != null) json.encodeToString(rcvMsgInfo) else generalGetString(MR.strings.message_queue_info_none) + return generalGetString(MR.strings.message_queue_info_server_info).format(json.encodeToString(qInfo), msgInfo) +} + @Preview @Composable fun PreviewChatInfoLayout() { 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 e00592bce9..1e564db134 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 @@ -42,7 +42,7 @@ sealed class CIInfoTab { @Composable fun ChatItemInfoView(chatRh: Long?, ci: ChatItem, ciInfo: ChatItemInfo, devTools: Boolean) { val sent = ci.chatDir.sent - val appColors = CurrentColors.collectAsState().value.appColors + val appColors = MaterialTheme.appColors val uriHandler = LocalUriHandler.current val selection = remember { mutableStateOf(CIInfoTab.History) } 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 6c3a884a0e..71d82b5691 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 @@ -5,25 +5,25 @@ import androidx.compose.foundation.* import androidx.compose.foundation.gestures.* import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.* -import androidx.compose.foundation.shape.CircleShape import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.runtime.saveable.mapSaver import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.* -import androidx.compose.ui.draw.clip -import androidx.compose.ui.graphics.Color +import androidx.compose.ui.draw.drawBehind +import androidx.compose.ui.draw.drawWithCache +import androidx.compose.ui.graphics.* import androidx.compose.ui.platform.* import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.intl.Locale import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.graphics.ImageBitmap import androidx.compose.ui.text.* import androidx.compose.ui.unit.* import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.ui.theme.* import chat.simplex.common.views.call.* @@ -118,369 +118,381 @@ fun ChatView(chatId: String, chatModel: ChatModel, onComposed: suspend (chatId: val clipboard = LocalClipboardManager.current when (chat.chatInfo) { is ChatInfo.Direct, is ChatInfo.Group, is ChatInfo.Local -> { - ChatLayout( - chat, - unreadCount, - composeState, - composeView = { - Column( - Modifier.fillMaxWidth(), - horizontalAlignment = Alignment.CenterHorizontally - ) { - if ( - chat.chatInfo is ChatInfo.Direct - && !chat.chatInfo.contact.ready - && chat.chatInfo.contact.active - && !chat.chatInfo.contact.nextSendGrpInv + val perChatTheme = remember(chat.chatInfo, CurrentColors.value.base) { if (chat.chatInfo is ChatInfo.Direct) chat.chatInfo.contact.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else if (chat.chatInfo is ChatInfo.Group) chat.chatInfo.groupInfo.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) else null } + val overrides = if (perChatTheme != null) ThemeManager.currentColors(null, perChatTheme, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) else null + SimpleXThemeOverride(overrides ?: CurrentColors.collectAsState().value) { + ChatLayout( + chat, + unreadCount, + composeState, + composeView = { + Column( + Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally ) { - Text( - generalGetString(MR.strings.contact_connection_pending), - Modifier.padding(top = 4.dp), - fontSize = 14.sp, - color = MaterialTheme.colors.secondary + if ( + chat.chatInfo is ChatInfo.Direct + && !chat.chatInfo.contact.ready + && chat.chatInfo.contact.active + && !chat.chatInfo.contact.nextSendGrpInv + ) { + Text( + generalGetString(MR.strings.contact_connection_pending), + Modifier.padding(top = 4.dp), + fontSize = 14.sp, + color = MaterialTheme.colors.secondary + ) + } + ComposeView( + chatModel, chat, composeState, attachmentOption, + showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } } ) } - ComposeView( - chatModel, chat, composeState, attachmentOption, - showChooseAttachment = { scope.launch { attachmentBottomSheetState.show() } } - ) - } - }, - attachmentOption, - attachmentBottomSheetState, - searchText, - useLinkPreviews = useLinkPreviews, - linkMode = chatModel.simplexLinkMode.value, - back = { - hideKeyboard(view) - AudioPlayer.stop() - chatModel.chatId.value = null - chatModel.groupMembers.clear() - }, - info = { - if (ModalManager.end.hasModalsOpen()) { - ModalManager.end.closeModals() - return@ChatLayout - } - hideKeyboard(view) - withBGApi { - // The idea is to preload information before showing a modal because large groups can take time to load all members - var preloadedContactInfo: Pair? = null - var preloadedCode: String? = null - var preloadedLink: Pair? = null - if (chat.chatInfo is ChatInfo.Direct) { - preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) - preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second - } else if (chat.chatInfo is ChatInfo.Group) { - setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel) - preloadedLink = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId) + }, + attachmentOption, + attachmentBottomSheetState, + searchText, + useLinkPreviews = useLinkPreviews, + linkMode = chatModel.simplexLinkMode.value, + back = { + hideKeyboard(view) + AudioPlayer.stop() + chatModel.chatId.value = null + chatModel.groupMembers.clear() + }, + info = { + if (ModalManager.end.hasModalsOpen()) { + ModalManager.end.closeModals() + return@ChatLayout } - ModalManager.end.showModalCloseable(true) { close -> - val chat = remember { activeChat }.value - if (chat?.chatInfo is ChatInfo.Direct) { - var contactInfo: Pair? by remember { mutableStateOf(preloadedContactInfo) } - var code: String? by remember { mutableStateOf(preloadedCode) } - KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) { - contactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) - preloadedContactInfo = contactInfo - code = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second - preloadedCode = code - } - ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close) - } else if (chat?.chatInfo is ChatInfo.Group) { - var link: Pair? by remember(chat.id) { mutableStateOf(preloadedLink) } - KeyChangeEffect(chat.id) { - setGroupMembers(chatRh, (chat.chatInfo as ChatInfo.Group).groupInfo, chatModel) - link = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId) - preloadedLink = link - } - GroupChatInfoView(chatModel, chatRh, chat.id, link?.first, link?.second, { - link = it - preloadedLink = it - }, close) - } - } - } - }, - showMemberInfo = { groupInfo: GroupInfo, member: GroupMember -> - hideKeyboard(view) - withBGApi { - val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId) - val stats = r?.second - val (_, code) = if (member.memberActive) { - val memCode = chatModel.controller.apiGetGroupMemberCode(chatRh, groupInfo.apiId, member.groupMemberId) - member to memCode?.second - } else { - member to null - } - setGroupMembers(chatRh, groupInfo, chatModel) - ModalManager.end.closeModals() - ModalManager.end.showModalCloseable(true) { close -> - remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem -> - GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close) - } - } - } - }, - loadPrevMessages = { - if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout - val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) - val firstId = chatModel.chatItems.value.firstOrNull()?.id - if (c != null && firstId != null) { + hideKeyboard(view) withBGApi { - apiLoadPrevMessages(c, chatModel, firstId, searchText.value) - } - } - }, - deleteMessage = { itemId, mode -> - withBGApi { - val cInfo = chat.chatInfo - val toDeleteItem = chatModel.chatItems.value.firstOrNull { it.id == itemId } - val toModerate = toDeleteItem?.memberToModerate(chat.chatInfo) - val groupInfo = toModerate?.first - val groupMember = toModerate?.second - val deletedChatItem: ChatItem? - val toChatItem: ChatItem? - if (mode == CIDeleteMode.cidmBroadcast && groupInfo != null && groupMember != null) { - val r = chatModel.controller.apiDeleteMemberChatItem( - chatRh, - groupId = groupInfo.groupId, - groupMemberId = groupMember.groupMemberId, - itemId = itemId - ) - deletedChatItem = r?.first - toChatItem = r?.second - } else { - val r = chatModel.controller.apiDeleteChatItem( - chatRh, - type = cInfo.chatType, - id = cInfo.apiId, - itemId = itemId, - mode = mode - ) - deletedChatItem = r?.deletedChatItem?.chatItem - toChatItem = r?.toChatItem?.chatItem - } - if (toChatItem == null && deletedChatItem != null) { - chatModel.removeChatItem(chatRh, cInfo, deletedChatItem) - } else if (toChatItem != null) { - chatModel.upsertChatItem(chatRh, cInfo, toChatItem) - } - } - }, - deleteMessages = { itemIds -> - if (itemIds.isNotEmpty()) { - val chatInfo = chat.chatInfo - withBGApi { - val deletedItems: ArrayList = arrayListOf() - for (itemId in itemIds) { - val di = chatModel.controller.apiDeleteChatItem( - chatRh, chatInfo.chatType, chatInfo.apiId, itemId, CIDeleteMode.cidmInternal - )?.deletedChatItem?.chatItem - if (di != null) { - deletedItems.add(di) - } - } - for (di in deletedItems) { - chatModel.removeChatItem(chatRh, chatInfo, di) - } - } - } - }, - receiveFile = { fileId -> - withBGApi { chatModel.controller.receiveFile(chatRh, user, fileId) } - }, - cancelFile = { fileId -> - withBGApi { chatModel.controller.cancelFile(chatRh, user, fileId) } - }, - joinGroup = { groupId, onComplete -> - withBGApi { - chatModel.controller.apiJoinGroup(chatRh, groupId) - onComplete.invoke() - } - }, - startCall = out@{ media -> - withBGApi { - val cInfo = chat.chatInfo - if (cInfo is ChatInfo.Direct) { - 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)) - } - } - }, - endCall = { - val call = chatModel.activeCall.value - if (call != null) withBGApi { chatModel.callManager.endCall(call) } - }, - acceptCall = { contact -> - hideKeyboard(view) - withBGApi { - val invitation = chatModel.callInvitations.remove(contact.id) - ?: controller.apiGetCallInvitations(chatModel.remoteHostId()).firstOrNull { it.contact.id == contact.id } - if (invitation == null) { - AlertManager.shared.showAlertMsg(generalGetString(MR.strings.call_already_ended)) - } else { - chatModel.callManager.acceptIncomingCall(invitation = invitation) - } - } - }, - acceptFeature = { contact, feature, param -> - withBGApi { - chatModel.controller.allowFeatureToContact(chatRh, contact, feature, param) - } - }, - openDirectChat = { contactId -> - withBGApi { - openDirectChat(chatRh, contactId, chatModel) - } - }, - updateContactStats = { contact -> - withBGApi { - val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) - if (r != null) { - val contactStats = r.first - if (contactStats != null) - chatModel.updateContactConnectionStats(chatRh, contact, contactStats) - } - } - }, - updateMemberStats = { groupInfo, member -> - withBGApi { - val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId) - if (r != null) { - val memStats = r.second - if (memStats != null) { - chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats) - } - } - } - }, - syncContactConnection = { contact -> - withBGApi { - val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false) - if (cStats != null) { - chatModel.updateContactConnectionStats(chatRh, contact, cStats) - } - } - }, - syncMemberConnection = { groupInfo, member -> - withBGApi { - val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false) - if (r != null) { - chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second) - } - } - }, - findModelChat = { chatId -> - chatModel.getChat(chatId) - }, - findModelMember = { memberId -> - chatModel.groupMembers.find { it.id == memberId } - }, - setReaction = { cInfo, cItem, add, reaction -> - withBGApi { - val updatedCI = chatModel.controller.apiChatItemReaction( - rh = chatRh, - type = cInfo.chatType, - id = cInfo.apiId, - itemId = cItem.id, - add = add, - reaction = reaction - ) - if (updatedCI != null) { - chatModel.updateChatItem(cInfo, updatedCI) - } - } - }, - showItemDetails = { cInfo, cItem -> - suspend fun loadChatItemInfo(): ChatItemInfo? { - val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id) - if (ciInfo != null) { - if (chat.chatInfo is ChatInfo.Group) { + // The idea is to preload information before showing a modal because large groups can take time to load all members + var preloadedContactInfo: Pair? = null + var preloadedCode: String? = null + var preloadedLink: Pair? = null + if (chat.chatInfo is ChatInfo.Direct) { + preloadedContactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) + preloadedCode = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second + } else if (chat.chatInfo is ChatInfo.Group) { setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel) + preloadedLink = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId) } - } - return ciInfo - } - withBGApi { - var initialCiInfo = loadChatItemInfo() ?: return@withBGApi - ModalManager.end.closeModals() - ModalManager.end.showModalCloseable(endButtons = { - ShareButton { - clipboard.shareText(itemInfoShareText(chatModel, cItem, initialCiInfo, chatModel.controller.appPrefs.developerTools.get())) - } - }) { close -> - var ciInfo by remember(cItem.id) { mutableStateOf(initialCiInfo) } - ChatItemInfoView(chatRh, cItem, ciInfo, devTools = chatModel.controller.appPrefs.developerTools.get()) - LaunchedEffect(cItem.id) { - withContext(Dispatchers.Default) { - for (apiResp in controller.messagesChannel) { - val msg = apiResp.resp - if (apiResp.remoteHostId == chatRh && - msg is CR.ChatItemStatusUpdated && - msg.chatItem.chatItem.id == cItem.id - ) { - ciInfo = loadChatItemInfo() ?: return@withContext - initialCiInfo = ciInfo - } + ModalManager.end.showModalCloseable(true) { close -> + val chat = remember { activeChat }.value + if (chat?.chatInfo is ChatInfo.Direct) { + var contactInfo: Pair? by remember { mutableStateOf(preloadedContactInfo) } + var code: String? by remember { mutableStateOf(preloadedCode) } + KeyChangeEffect(chat.id, ChatModel.networkStatuses.toMap()) { + contactInfo = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) + preloadedContactInfo = contactInfo + code = chatModel.controller.apiGetContactCode(chatRh, chat.chatInfo.apiId)?.second + preloadedCode = code + } + ChatInfoView(chatModel, (chat.chatInfo as ChatInfo.Direct).contact, contactInfo?.first, contactInfo?.second, chat.chatInfo.localAlias, code, close) + } else if (chat?.chatInfo is ChatInfo.Group) { + var link: Pair? by remember(chat.id) { mutableStateOf(preloadedLink) } + KeyChangeEffect(chat.id) { + setGroupMembers(chatRh, (chat.chatInfo as ChatInfo.Group).groupInfo, chatModel) + link = chatModel.controller.apiGetGroupLink(chatRh, chat.chatInfo.groupInfo.groupId) + preloadedLink = link + } + GroupChatInfoView(chatModel, chatRh, chat.id, link?.first, link?.second, { + link = it + preloadedLink = it + }, close) + } else { + LaunchedEffect(Unit) { + close() } } } - KeyChangeEffect(chatModel.chatId.value) { - close() + } + }, + showMemberInfo = { groupInfo: GroupInfo, member: GroupMember -> + hideKeyboard(view) + withBGApi { + val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId) + val stats = r?.second + val (_, code) = if (member.memberActive) { + val memCode = chatModel.controller.apiGetGroupMemberCode(chatRh, groupInfo.apiId, member.groupMemberId) + member to memCode?.second + } else { + member to null + } + setGroupMembers(chatRh, groupInfo, chatModel) + ModalManager.end.closeModals() + ModalManager.end.showModalCloseable(true) { close -> + remember { derivedStateOf { chatModel.getGroupMember(member.groupMemberId) } }.value?.let { mem -> + GroupMemberInfoView(chatRh, groupInfo, mem, stats, code, chatModel, close, close) + } } } - } - }, - addMembers = { groupInfo -> - hideKeyboard(view) - withBGApi { - setGroupMembers(chatRh, groupInfo, chatModel) - ModalManager.end.closeModals() - ModalManager.end.showModalCloseable(true) { close -> - AddGroupMembersView(chatRh, groupInfo, false, chatModel, close) + }, + loadPrevMessages = { + if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout + val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) + val firstId = chatModel.chatItems.value.firstOrNull()?.id + if (c != null && firstId != null) { + withBGApi { + apiLoadPrevMessages(c, chatModel, firstId, searchText.value) + } } - } - }, - openGroupLink = { groupInfo -> - hideKeyboard(view) - withBGApi { - val link = chatModel.controller.apiGetGroupLink(chatRh, groupInfo.groupId) - ModalManager.end.closeModals() - ModalManager.end.showModalCloseable(true) { - GroupLinkView(chatModel, chatRh, groupInfo, link?.first, link?.second, onGroupLinkUpdated = null) + }, + deleteMessage = { itemId, mode -> + withBGApi { + val cInfo = chat.chatInfo + val toDeleteItem = chatModel.chatItems.value.firstOrNull { it.id == itemId } + val toModerate = toDeleteItem?.memberToModerate(chat.chatInfo) + val groupInfo = toModerate?.first + val groupMember = toModerate?.second + val deletedChatItem: ChatItem? + val toChatItem: ChatItem? + if (mode == CIDeleteMode.cidmBroadcast && groupInfo != null && groupMember != null) { + val r = chatModel.controller.apiDeleteMemberChatItem( + chatRh, + groupId = groupInfo.groupId, + groupMemberId = groupMember.groupMemberId, + itemId = itemId + ) + deletedChatItem = r?.first + toChatItem = r?.second + } else { + val r = chatModel.controller.apiDeleteChatItem( + chatRh, + type = cInfo.chatType, + id = cInfo.apiId, + itemId = itemId, + mode = mode + ) + deletedChatItem = r?.deletedChatItem?.chatItem + toChatItem = r?.toChatItem?.chatItem + } + if (toChatItem == null && deletedChatItem != null) { + chatModel.removeChatItem(chatRh, cInfo, deletedChatItem) + } else if (toChatItem != null) { + chatModel.upsertChatItem(chatRh, cInfo, toChatItem) + } } - } - }, - markRead = { range, unreadCountAfter -> - chatModel.markChatItemsRead(chat, range, unreadCountAfter) - ntfManager.cancelNotificationsForChat(chat.id) - withBGApi { - chatModel.controller.apiChatRead( - chatRh, - chat.chatInfo.chatType, - chat.chatInfo.apiId, - range - ) - } - }, - changeNtfsState = { enabled, currentValue -> toggleNotifications(chat, enabled, chatModel, currentValue) }, - onSearchValueChanged = { value -> - if (searchText.value == value) return@ChatLayout - if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout - val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout - withBGApi { - apiFindMessages(c, chatModel, value) - searchText.value = value - } - }, - onComposed, - developerTools = chatModel.controller.appPrefs.developerTools.get(), - showViaProxy = chatModel.controller.appPrefs.showSentViaProxy.get(), - ) + }, + deleteMessages = { itemIds -> + if (itemIds.isNotEmpty()) { + val chatInfo = chat.chatInfo + withBGApi { + val deletedItems: ArrayList = arrayListOf() + for (itemId in itemIds) { + val di = chatModel.controller.apiDeleteChatItem( + chatRh, chatInfo.chatType, chatInfo.apiId, itemId, CIDeleteMode.cidmInternal + )?.deletedChatItem?.chatItem + if (di != null) { + deletedItems.add(di) + } + } + for (di in deletedItems) { + chatModel.removeChatItem(chatRh, chatInfo, di) + } + } + } + }, + receiveFile = { fileId -> + withBGApi { chatModel.controller.receiveFile(chatRh, user, fileId) } + }, + cancelFile = { fileId -> + withBGApi { chatModel.controller.cancelFile(chatRh, user, fileId) } + }, + joinGroup = { groupId, onComplete -> + withBGApi { + chatModel.controller.apiJoinGroup(chatRh, groupId) + onComplete.invoke() + } + }, + startCall = out@{ media -> + withBGApi { + val cInfo = chat.chatInfo + if (cInfo is ChatInfo.Direct) { + 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)) + } + } + }, + endCall = { + val call = chatModel.activeCall.value + if (call != null) withBGApi { chatModel.callManager.endCall(call) } + }, + acceptCall = { contact -> + hideKeyboard(view) + withBGApi { + val invitation = chatModel.callInvitations.remove(contact.id) + ?: controller.apiGetCallInvitations(chatModel.remoteHostId()).firstOrNull { it.contact.id == contact.id } + if (invitation == null) { + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.call_already_ended)) + } else { + chatModel.callManager.acceptIncomingCall(invitation = invitation) + } + } + }, + acceptFeature = { contact, feature, param -> + withBGApi { + chatModel.controller.allowFeatureToContact(chatRh, contact, feature, param) + } + }, + openDirectChat = { contactId -> + withBGApi { + openDirectChat(chatRh, contactId, chatModel) + } + }, + forwardItem = { cItem, cInfo -> + chatModel.chatId.value = null + chatModel.sharedContent.value = SharedContent.Forward(cInfo, cItem) + }, + updateContactStats = { contact -> + withBGApi { + val r = chatModel.controller.apiContactInfo(chatRh, chat.chatInfo.apiId) + if (r != null) { + val contactStats = r.first + if (contactStats != null) + chatModel.updateContactConnectionStats(chatRh, contact, contactStats) + } + } + }, + updateMemberStats = { groupInfo, member -> + withBGApi { + val r = chatModel.controller.apiGroupMemberInfo(chatRh, groupInfo.groupId, member.groupMemberId) + if (r != null) { + val memStats = r.second + if (memStats != null) { + chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, memStats) + } + } + } + }, + syncContactConnection = { contact -> + withBGApi { + val cStats = chatModel.controller.apiSyncContactRatchet(chatRh, contact.contactId, force = false) + if (cStats != null) { + chatModel.updateContactConnectionStats(chatRh, contact, cStats) + } + } + }, + syncMemberConnection = { groupInfo, member -> + withBGApi { + val r = chatModel.controller.apiSyncGroupMemberRatchet(chatRh, groupInfo.apiId, member.groupMemberId, force = false) + if (r != null) { + chatModel.updateGroupMemberConnectionStats(chatRh, groupInfo, r.first, r.second) + } + } + }, + findModelChat = { chatId -> + chatModel.getChat(chatId) + }, + findModelMember = { memberId -> + chatModel.groupMembers.find { it.id == memberId } + }, + setReaction = { cInfo, cItem, add, reaction -> + withBGApi { + val updatedCI = chatModel.controller.apiChatItemReaction( + rh = chatRh, + type = cInfo.chatType, + id = cInfo.apiId, + itemId = cItem.id, + add = add, + reaction = reaction + ) + if (updatedCI != null) { + chatModel.updateChatItem(cInfo, updatedCI) + } + } + }, + showItemDetails = { cInfo, cItem -> + suspend fun loadChatItemInfo(): ChatItemInfo? { + val ciInfo = chatModel.controller.apiGetChatItemInfo(chatRh, cInfo.chatType, cInfo.apiId, cItem.id) + if (ciInfo != null) { + if (chat.chatInfo is ChatInfo.Group) { + setGroupMembers(chatRh, chat.chatInfo.groupInfo, chatModel) + } + } + return ciInfo + } + withBGApi { + var initialCiInfo = loadChatItemInfo() ?: return@withBGApi + ModalManager.end.closeModals() + ModalManager.end.showModalCloseable(endButtons = { + ShareButton { + clipboard.shareText(itemInfoShareText(chatModel, cItem, initialCiInfo, chatModel.controller.appPrefs.developerTools.get())) + } + }) { close -> + var ciInfo by remember(cItem.id) { mutableStateOf(initialCiInfo) } + ChatItemInfoView(chatRh, cItem, ciInfo, devTools = chatModel.controller.appPrefs.developerTools.get()) + LaunchedEffect(cItem.id) { + withContext(Dispatchers.Default) { + for (apiResp in controller.messagesChannel) { + val msg = apiResp.resp + if (apiResp.remoteHostId == chatRh && + msg is CR.ChatItemStatusUpdated && + msg.chatItem.chatItem.id == cItem.id + ) { + ciInfo = loadChatItemInfo() ?: return@withContext + initialCiInfo = ciInfo + } + } + } + } + KeyChangeEffect(chatModel.chatId.value) { + close() + } + } + } + }, + addMembers = { groupInfo -> + hideKeyboard(view) + withBGApi { + setGroupMembers(chatRh, groupInfo, chatModel) + ModalManager.end.closeModals() + ModalManager.end.showModalCloseable(true) { close -> + AddGroupMembersView(chatRh, groupInfo, false, chatModel, close) + } + } + }, + openGroupLink = { groupInfo -> + hideKeyboard(view) + withBGApi { + val link = chatModel.controller.apiGetGroupLink(chatRh, groupInfo.groupId) + ModalManager.end.closeModals() + ModalManager.end.showModalCloseable(true) { + GroupLinkView(chatModel, chatRh, groupInfo, link?.first, link?.second, onGroupLinkUpdated = null) + } + } + }, + markRead = { range, unreadCountAfter -> + chatModel.markChatItemsRead(chat, range, unreadCountAfter) + ntfManager.cancelNotificationsForChat(chat.id) + withBGApi { + chatModel.controller.apiChatRead( + chatRh, + chat.chatInfo.chatType, + chat.chatInfo.apiId, + range + ) + } + }, + changeNtfsState = { enabled, currentValue -> toggleNotifications(chat, enabled, chatModel, currentValue) }, + onSearchValueChanged = { value -> + if (searchText.value == value) return@ChatLayout + if (chatModel.chatId.value != activeChat.value?.id) return@ChatLayout + val c = chatModel.getChat(chatModel.chatId.value ?: return@ChatLayout) ?: return@ChatLayout + withBGApi { + apiFindMessages(c, chatModel, value) + searchText.value = value + } + }, + onComposed, + developerTools = chatModel.controller.appPrefs.developerTools.get(), + showViaProxy = chatModel.controller.appPrefs.showSentViaProxy.get(), + ) + } } is ChatInfo.ContactConnection -> { val close = { chatModel.chatId.value = null } @@ -534,6 +546,7 @@ fun ChatLayout( acceptCall: (Contact) -> Unit, acceptFeature: (Contact, ChatFeature, Int?) -> Unit, openDirectChat: (Long) -> Unit, + forwardItem: (ChatInfo, ChatItem) -> Unit, updateContactStats: (Contact) -> Unit, updateMemberStats: (GroupInfo, GroupMember) -> Unit, syncContactConnection: (Contact) -> Unit, @@ -598,15 +611,25 @@ fun ChatLayout( floatingActionButton = { floatingButton.value() }, contentColor = LocalContentColor.current, drawerContentColor = LocalContentColor.current, + backgroundColor = Color.Unspecified ) { contentPadding -> + val wallpaperImage = MaterialTheme.wallpaper.type.image + val wallpaperType = MaterialTheme.wallpaper.type + val backgroundColor = MaterialTheme.wallpaper.background ?: wallpaperType.defaultBackgroundColor(CurrentColors.value.base, MaterialTheme.colors.background) + val tintColor = MaterialTheme.wallpaper.tint ?: wallpaperType.defaultTintColor(CurrentColors.value.base) BoxWithConstraints(Modifier - .fillMaxHeight() + .fillMaxSize() + .background(MaterialTheme.colors.background) + .then(if (wallpaperImage != null) + Modifier.drawWithCache { chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor) } + else + Modifier) .padding(contentPadding) ) { ChatItemsList( chat, unreadCount, composeState, searchValue, useLinkPreviews, linkMode, showMemberInfo, loadPrevMessages, deleteMessage, deleteMessages, - receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, + receiveFile, cancelFile, joinGroup, acceptCall, acceptFeature, openDirectChat, forwardItem, updateContactStats, updateMemberStats, syncContactConnection, syncMemberConnection, findModelChat, findModelMember, setReaction, showItemDetails, markRead, setFloatingButton, onComposed, developerTools, showViaProxy, ) @@ -875,6 +898,7 @@ fun BoxWithConstraintsScope.ChatItemsList( acceptCall: (Contact) -> Unit, acceptFeature: (Contact, ChatFeature, Int?) -> Unit, openDirectChat: (Long) -> Unit, + forwardItem: (ChatInfo, ChatItem) -> Unit, updateContactStats: (Contact) -> Unit, updateMemberStats: (GroupInfo, GroupMember) -> Unit, syncContactConnection: (Contact) -> Unit, @@ -978,7 +1002,7 @@ fun BoxWithConstraintsScope.ChatItemsList( tryOrShowError("${cItem.id}ChatItem", error = { CIBrokenComposableView(if (cItem.chatDir.sent) Alignment.CenterEnd else Alignment.CenterStart) }) { - ChatItemView(chat.remoteHostId, chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools, showViaProxy = showViaProxy) + ChatItemView(chat.remoteHostId, chat.chatInfo, cItem, composeState, provider, useLinkPreviews = useLinkPreviews, linkMode = linkMode, revealed = revealed, range = range, deleteMessage = deleteMessage, deleteMessages = deleteMessages, receiveFile = receiveFile, cancelFile = cancelFile, joinGroup = joinGroup, acceptCall = acceptCall, acceptFeature = acceptFeature, openDirectChat = openDirectChat, forwardItem = forwardItem, updateContactStats = updateContactStats, updateMemberStats = updateMemberStats, syncContactConnection = syncContactConnection, syncMemberConnection = syncMemberConnection, findModelChat = findModelChat, findModelMember = findModelMember, scrollToItem = scrollToItem, setReaction = setReaction, showItemDetails = showItemDetails, developerTools = developerTools, showViaProxy = showViaProxy) } } @@ -1262,7 +1286,7 @@ val MEMBER_IMAGE_SIZE: Dp = 38.dp @Composable fun MemberImage(member: GroupMember) { - ProfileImage(MEMBER_IMAGE_SIZE, member.memberProfile.image) + ProfileImage(MEMBER_IMAGE_SIZE, member.memberProfile.image, backgroundColor = MaterialTheme.colors.background) } @Composable @@ -1531,6 +1555,7 @@ fun PreviewChatLayout() { acceptCall = { _ -> }, acceptFeature = { _, _, _ -> }, openDirectChat = { _ -> }, + forwardItem = { _, _ -> }, updateContactStats = { }, updateMemberStats = { _, _ -> }, syncContactConnection = { }, @@ -1604,6 +1629,7 @@ fun PreviewGroupChatLayout() { acceptCall = { _ -> }, acceptFeature = { _, _, _ -> }, openDirectChat = { _ -> }, + forwardItem = { _, _ -> }, updateContactStats = { }, updateMemberStats = { _, _ -> }, syncContactConnection = { }, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextInvitingContactMemberView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextInvitingContactMemberView.kt index 20316dd524..bc82bc593f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextInvitingContactMemberView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeContextInvitingContactMemberView.kt @@ -16,7 +16,7 @@ import dev.icerock.moko.resources.compose.stringResource @Composable fun ComposeContextInvitingContactMemberView() { - val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage + val sentColor = MaterialTheme.appColors.sentMessage Row( Modifier .height(60.dp) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeFileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeFileView.kt index 83076f885b..7ab7963547 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeFileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeFileView.kt @@ -16,7 +16,7 @@ import chat.simplex.res.MR @Composable fun ComposeFileView(fileName: String, cancelFile: () -> Unit, cancelEnabled: Boolean) { - val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage + val sentColor = MaterialTheme.appColors.sentMessage Row( Modifier .height(60.dp) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeImageView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeImageView.kt index 906065f741..97b6f9afda 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeImageView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeImageView.kt @@ -20,7 +20,7 @@ import chat.simplex.common.views.helpers.UploadContent @Composable fun ComposeImageView(media: ComposePreview.MediaPreview, cancelImages: () -> Unit, cancelEnabled: Boolean) { - val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage + val sentColor = MaterialTheme.appColors.sentMessage Row( Modifier .padding(top = 8.dp) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt index d9ea35096b..24533247ba 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeView.kt @@ -776,7 +776,7 @@ fun ComposeView( @Composable fun MsgNotAllowedView(reason: String, icon: Painter) { - val color = CurrentColors.collectAsState().value.appColors.receivedMessage + val color = MaterialTheme.appColors.receivedMessage Row(Modifier.padding(top = 5.dp).fillMaxWidth().background(color).padding(horizontal = DEFAULT_PADDING_HALF, vertical = DEFAULT_PADDING_HALF * 1.5f), verticalAlignment = Alignment.CenterVertically) { Icon(icon, null, tint = MaterialTheme.colors.secondary) Spacer(Modifier.width(DEFAULT_PADDING_HALF)) @@ -862,7 +862,7 @@ fun ComposeView( } } Row( - modifier = Modifier.padding(end = 8.dp), + modifier = Modifier.background(MaterialTheme.colors.background).padding(end = 8.dp), verticalAlignment = Alignment.Bottom, ) { val isGroupAndProhibitedFiles = chat.chatInfo is ChatInfo.Group && !chat.chatInfo.groupInfo.fullGroupPreferences.files.on(chat.chatInfo.groupInfo.membership) @@ -974,7 +974,7 @@ fun ComposeView( val timedMessageAllowed = remember(chat.chatInfo) { chat.chatInfo.featureEnabled(ChatFeature.TimedMessages) } val sendButtonColor = if (chat.chatInfo.incognito) - if (isSystemInDarkTheme()) Indigo else Indigo.copy(alpha = 0.7F) + if (isInDarkTheme()) Indigo else Indigo.copy(alpha = 0.7F) else MaterialTheme.colors.primary SendMsgView( composeState, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeVoiceView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeVoiceView.kt index a4c90d30dd..b71d090a4e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeVoiceView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ComposeVoiceView.kt @@ -35,7 +35,7 @@ fun ComposeVoiceView( ) { val progress = rememberSaveable { mutableStateOf(0) } val duration = rememberSaveable(recordedDurationMs) { mutableStateOf(recordedDurationMs) } - val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage + val sentColor = MaterialTheme.appColors.sentMessage Box { Box( Modifier diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt index ce34ecf0c3..0c4efa7d0d 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/ContextItemView.kt @@ -30,8 +30,8 @@ fun ContextItemView( cancelContextItem: () -> Unit ) { val sent = contextItem.chatDir.sent - val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage - val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage + val sentColor = MaterialTheme.appColors.sentMessage + val receivedColor = MaterialTheme.appColors.receivedMessage @Composable fun MessageText(attachment: ImageResource?, lines: Int) { 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 d546b51a93..931cc17872 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 @@ -86,7 +86,7 @@ fun getContactsToAdd(chatModel: ChatModel, search: String): List { .map { it.chatInfo } .filterIsInstance() .map { it.contact } - .filter { c -> c.ready && c.active && c.contactId !in memberContactIds && c.chatViewName.lowercase().contains(s) } + .filter { c -> c.sendMsgEnabled && !c.nextSendGrpInv && c.contactId !in memberContactIds && c.chatViewName.lowercase().contains(s) } .sortedBy { it.displayName.lowercase() } .toList() } 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 c52dd941fd..b60c1f2496 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 @@ -9,7 +9,6 @@ import SectionSpacer import SectionTextFooter import SectionView import androidx.compose.desktop.ui.tooling.preview.Preview -import androidx.compose.foundation.combinedClickable import androidx.compose.foundation.layout.* import androidx.compose.foundation.lazy.* import androidx.compose.material.* @@ -204,7 +203,7 @@ fun GroupChatInfoLayout( scope.launch { listState.scrollToItem(0) } } val searchText = rememberSaveable(stateSaver = TextFieldValue.Saver) { mutableStateOf(TextFieldValue()) } - val filteredMembers = remember(members) { derivedStateOf { members.filter { it.chatViewName.lowercase().contains(searchText.value.text.trim()) } } } + val filteredMembers = remember(members) { derivedStateOf { members.filter { it.chatViewName.lowercase().contains(searchText.value.text.trim().lowercase()) } } } // LALAL strange scrolling LazyColumnWithScrollBar( Modifier @@ -233,6 +232,16 @@ fun GroupChatInfoLayout( } else { SendReceiptsOptionDisabled() } + + WallpaperButton { + ModalManager.end.showModal { + val chat = remember { derivedStateOf { chatModel.chats.firstOrNull { it.id == chat.id } } } + val c = chat.value + if (c != null) { + ChatWallpaperEditorModal(c) + } + } + } } SectionTextFooter(stringResource(MR.strings.only_group_owners_can_change_prefs)) SectionDividerSpaced(maxTopPadding = true) 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 e90efa7d1b..2799904b71 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 @@ -3,6 +3,7 @@ package chat.simplex.common.views.chat.group import InfoRow import SectionBottomSpacer import SectionDividerSpaced +import SectionItemView import SectionSpacer import SectionTextFooter import SectionView @@ -27,6 +28,7 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.ui.theme.* import chat.simplex.common.views.chat.* import chat.simplex.common.views.helpers.* @@ -58,6 +60,7 @@ fun GroupMemberInfoView( if (chat != null) { val newRole = remember { mutableStateOf(member.memberRole) } GroupMemberInfoLayout( + rhId = rhId, groupInfo, member, connStats, @@ -219,6 +222,7 @@ fun removeMemberDialog(rhId: Long?, groupInfo: GroupInfo, member: GroupMember, c @Composable fun GroupMemberInfoLayout( + rhId: Long?, groupInfo: GroupInfo, member: GroupMember, connStats: MutableState, @@ -397,6 +401,19 @@ fun GroupMemberInfoLayout( SectionView(title = stringResource(MR.strings.section_title_for_console)) { InfoRow(stringResource(MR.strings.info_row_local_name), member.localDisplayName) InfoRow(stringResource(MR.strings.info_row_database_id), member.groupMemberId.toString()) + SectionItemView({ + withBGApi { + val info = controller.apiGroupMemberQueueInfo(rhId, groupInfo.apiId, member.groupMemberId) + if (info != null) { + AlertManager.shared.showAlertMsg( + title = generalGetString(MR.strings.message_queue_info), + text = queueInfoText(info) + ) + } + } + }) { + Text(stringResource(MR.strings.info_row_debug_delivery)) + } } } SectionBottomSpacer() @@ -644,6 +661,7 @@ fun blockMemberForAll(rhId: Long?, gInfo: GroupInfo, member: GroupMember, blocke fun PreviewGroupMemberInfoLayout() { SimpleXTheme { GroupMemberInfoLayout( + rhId = null, groupInfo = GroupInfo.sampleData, member = GroupMember.sampleData, connStats = remember { mutableStateOf(null) }, 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 265d0cdeae..82646a99c5 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 @@ -123,13 +123,12 @@ private fun GroupPreferencesLayout( applyPrefs(preferences.copy(files = RoleGroupPreference(enable = enable, role))) } - // TODO enable simplexLinks preference in 5.8 -// SectionDividerSpaced(true, maxBottomPadding = false) -// val allowSimplexLinks = remember(preferences) { mutableStateOf(preferences.simplexLinks.enable) } -// val simplexLinksRole = remember(preferences) { mutableStateOf(preferences.simplexLinks.role) } -// FeatureSection(GroupFeature.SimplexLinks, allowSimplexLinks, simplexLinksRole, groupInfo, preferences, onTTLUpdated) { enable, role -> -// applyPrefs(preferences.copy(simplexLinks = RoleGroupPreference(enable = enable, role))) -// } + SectionDividerSpaced(true, maxBottomPadding = false) + val allowSimplexLinks = remember(preferences) { mutableStateOf(preferences.simplexLinks.enable) } + val simplexLinksRole = remember(preferences) { mutableStateOf(preferences.simplexLinks.role) } + FeatureSection(GroupFeature.SimplexLinks, allowSimplexLinks, simplexLinksRole, groupInfo, preferences, onTTLUpdated) { enable, role -> + applyPrefs(preferences.copy(simplexLinks = RoleGroupPreference(enable = enable, role))) + } SectionDividerSpaced(true, maxBottomPadding = false) val enableHistory = remember(preferences) { mutableStateOf(preferences.history.enable) } @@ -189,8 +188,6 @@ private fun FeatureSection( generalGetString(MR.strings.feature_enabled_for), featureRoles, enableForRole, - // remove in v5.8 - enabled = remember { mutableStateOf(false) }, onSelected = { value -> onSelected(enableFeature.value, value) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt index a48bb2bb12..0ce9ba32fc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIFileView.kt @@ -154,13 +154,7 @@ fun CIFileView( FileProtocol.SMP -> progressIndicator() FileProtocol.LOCAL -> {} } - is CIFileStatus.SndComplete -> { - if ((file.forwardingAllowed() || (chatModel.connectedToRemote() && CIFile.cachedRemoteFileRequests[file.fileSource] == true))) { - fileIcon() - } else { - fileIcon(innerIcon = painterResource(MR.images.ic_check_filled)) - } - } + is CIFileStatus.SndComplete -> fileIcon(innerIcon = painterResource(MR.images.ic_check_filled)) is CIFileStatus.SndCancelled -> fileIcon(innerIcon = painterResource(MR.images.ic_close)) is CIFileStatus.SndError -> fileIcon(innerIcon = painterResource(MR.images.ic_close)) is CIFileStatus.RcvInvitation -> diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt index a2338bb895..577327c159 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIGroupInvitationView.kt @@ -83,8 +83,8 @@ fun CIGroupInvitationView( } } - val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage - val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage + val sentColor = MaterialTheme.appColors.sentMessage + val receivedColor = MaterialTheme.appColors.receivedMessage Surface( modifier = if (action && !inProgress.value) Modifier.clickable(onClick = { inProgress.value = true @@ -110,6 +110,7 @@ fun CIGroupInvitationView( .padding(bottom = 4.dp), ) { groupInfoView() + val secondaryColor = MaterialTheme.colors.secondary Column(Modifier.padding(top = 2.dp, start = 5.dp)) { Divider(Modifier.fillMaxWidth().padding(bottom = 4.dp)) if (action) { @@ -117,7 +118,7 @@ fun CIGroupInvitationView( Text( buildAnnotatedString { append(generalGetString(if (chatIncognito) MR.strings.group_invitation_tap_to_join_incognito else MR.strings.group_invitation_tap_to_join)) - withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, timedMessagesTTL, encrypted = null, showStatus = false, showEdited = false)) } + withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, timedMessagesTTL, encrypted = null, showStatus = false, showEdited = false, secondaryColor = secondaryColor)) } }, color = if (inProgress.value) MaterialTheme.colors.secondary @@ -128,7 +129,7 @@ fun CIGroupInvitationView( Text( buildAnnotatedString { append(groupInvitationStr()) - withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, timedMessagesTTL, encrypted = null, showStatus = false, showEdited = false)) } + withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, timedMessagesTTL, encrypted = null, showStatus = false, showEdited = false, secondaryColor = secondaryColor)) } } ) } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt index 14fa6910da..def3b14ebc 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIMetaView.kt @@ -12,7 +12,6 @@ import androidx.compose.ui.text.style.TextOverflow import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import chat.simplex.common.ui.theme.CurrentColors import chat.simplex.common.model.* import chat.simplex.common.ui.theme.isInDarkTheme import chat.simplex.res.MR @@ -86,7 +85,7 @@ private fun CIMetaText( Spacer(Modifier.width(4.dp)) } if (showViaProxy && meta.sentViaProxy == true) { - Icon(painterResource(MR.images.ic_arrow_forward), null, Modifier.height(17.dp), tint = CurrentColors.value.colors.secondary) + Icon(painterResource(MR.images.ic_arrow_forward), null, Modifier.height(17.dp), tint = MaterialTheme.colors.secondary) } if (showStatus) { val statusIcon = meta.statusIcon(MaterialTheme.colors.primary, color, paleColor) @@ -115,6 +114,7 @@ fun reserveSpaceForMeta( meta: CIMeta, chatTTL: Int?, encrypted: Boolean?, + secondaryColor: Color, showStatus: Boolean = true, showEdited: Boolean = true, showViaProxy: Boolean = false @@ -132,7 +132,7 @@ fun reserveSpaceForMeta( if (showViaProxy && meta.sentViaProxy == true) { res += iconSpace } - if (showStatus && (meta.statusIcon(CurrentColors.value.colors.secondary) != null || !meta.disappearing)) { + if (showStatus && (meta.statusIcon(secondaryColor) != null || !meta.disappearing)) { res += iconSpace } if (encrypted != null) { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt index eb5d1e731a..dd0e9cf1a2 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIRcvDecryptionError.kt @@ -14,6 +14,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.* import chat.simplex.common.ui.theme.CurrentColors +import chat.simplex.common.ui.theme.appColors import chat.simplex.common.views.helpers.AlertManager import chat.simplex.common.views.helpers.generalGetString import chat.simplex.res.MR @@ -137,7 +138,7 @@ fun DecryptionErrorItemFixButton( onClick: () -> Unit, syncSupported: Boolean ) { - val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage + val receivedColor = MaterialTheme.appColors.receivedMessage Surface( Modifier.clickable(onClick = onClick), shape = RoundedCornerShape(18.dp), @@ -164,10 +165,11 @@ fun DecryptionErrorItemFixButton( tint = if (syncSupported) MaterialTheme.colors.primary else MaterialTheme.colors.secondary ) Spacer(Modifier.padding(2.dp)) + val secondaryColor = MaterialTheme.colors.secondary Text( buildAnnotatedString { append(generalGetString(MR.strings.fix_connection)) - withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, null, encrypted = null)) } + withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, null, encrypted = null, secondaryColor = secondaryColor)) } withStyle(reserveTimestampStyle) { append(" ") } // for icon }, color = if (syncSupported) MaterialTheme.colors.primary else MaterialTheme.colors.secondary @@ -184,7 +186,7 @@ fun DecryptionErrorItem( ci: ChatItem, onClick: () -> Unit ) { - val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage + val receivedColor = MaterialTheme.appColors.receivedMessage Surface( Modifier.clickable(onClick = onClick), shape = RoundedCornerShape(18.dp), @@ -195,10 +197,11 @@ fun DecryptionErrorItem( Modifier.padding(vertical = 6.dp, horizontal = 12.dp), contentAlignment = Alignment.BottomEnd, ) { + val secondaryColor = MaterialTheme.colors.secondary Text( buildAnnotatedString { withStyle(SpanStyle(fontStyle = FontStyle.Italic, color = Color.Red)) { append(ci.content.text) } - withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, null, encrypted = null)) } + withStyle(reserveTimestampStyle) { append(reserveSpaceForMeta(ci.meta, null, encrypted = null, secondaryColor = secondaryColor)) } }, style = MaterialTheme.typography.body1.copy(lineHeight = 22.sp) ) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt index f973a6ea66..96aaf586e9 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/CIVoiceView.kt @@ -154,8 +154,8 @@ private fun VoiceLayout( } when { hasText -> { - val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage - val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage + val sentColor = MaterialTheme.appColors.sentMessage + val receivedColor = MaterialTheme.appColors.receivedMessage Spacer(Modifier.width(6.dp)) VoiceMsgIndicator(file, audioPlaying.value, sent, hasText, progress, duration, brokenAudio, play, pause, longClick, receiveFile) Row(verticalAlignment = Alignment.CenterVertically) { @@ -224,8 +224,8 @@ private fun PlayPauseButton( longClick: () -> Unit, icon: ImageResource = MR.images.ic_play_arrow_filled, ) { - val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage - val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage + val sentColor = MaterialTheme.appColors.sentMessage + val receivedColor = MaterialTheme.appColors.receivedMessage Surface( Modifier.drawRingModifier(angle, strokeColor, strokeWidth), color = if (sent) sentColor else receivedColor, 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 0bc0415590..c195a1a299 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 @@ -59,6 +59,7 @@ fun ChatItemView( scrollToItem: (Long) -> Unit, acceptFeature: (Contact, ChatFeature, Int?) -> Unit, openDirectChat: (Long) -> Unit, + forwardItem: (ChatInfo, ChatItem) -> Unit, updateContactStats: (Contact) -> Unit, updateMemberStats: (GroupInfo, GroupMember) -> Unit, syncContactConnection: (Contact) -> Unit, @@ -68,7 +69,8 @@ fun ChatItemView( setReaction: (ChatInfo, ChatItem, Boolean, MsgReaction) -> Unit, showItemDetails: (ChatInfo, ChatItem) -> Unit, developerTools: Boolean, - showViaProxy: Boolean + showViaProxy: Boolean, + preview: Boolean = false, ) { val uriHandler = LocalUriHandler.current val sent = cItem.chatDir.sent @@ -260,8 +262,7 @@ fun ChatItemView( !cItem.isLiveDummy && !live ) { ItemAction(stringResource(MR.strings.forward_chat_item), painterResource(MR.images.ic_forward), onClick = { - chatModel.chatId.value = null - chatModel.sharedContent.value = SharedContent.Forward(cItem, cInfo) + forwardItem(cInfo, cItem) showMenu.value = false }) } @@ -272,7 +273,7 @@ fun ChatItemView( if (cItem.meta.itemDeleted == null && cItem.file != null && cItem.file.cancelAction != null && !cItem.localNote) { CancelFileItemAction(cItem.file.fileId, showMenu, cancelFile = cancelFile, cancelAction = cItem.file.cancelAction) } - if (!(live && cItem.meta.isLive)) { + if (!(live && cItem.meta.isLive) && !preview) { DeleteItemAction(cItem, revealed, showMenu, questionText = deleteMessageQuestionText(), deleteMessage, deleteMessages) } val groupInfo = cItem.memberToModerate(cInfo)?.first @@ -819,40 +820,40 @@ expect fun copyItemToClipboard(cItem: ChatItem, clipboard: ClipboardManager) @Preview @Composable -fun PreviewChatItemView() { - SimpleXTheme { - ChatItemView( - rhId = null, - ChatInfo.Direct.sampleData, - ChatItem.getSampleData( - 1, CIDirection.DirectSnd(), Clock.System.now(), "hello" - ), - useLinkPreviews = true, - linkMode = SimplexLinkMode.DESCRIPTION, - composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, - revealed = remember { mutableStateOf(false) }, - range = 0..1, - deleteMessage = { _, _ -> }, - deleteMessages = { _ -> }, - receiveFile = { _ -> }, - cancelFile = {}, - joinGroup = { _, _ -> }, - acceptCall = { _ -> }, - scrollToItem = {}, - acceptFeature = { _, _, _ -> }, - openDirectChat = { _ -> }, - updateContactStats = { }, - updateMemberStats = { _, _ -> }, - syncContactConnection = { }, - syncMemberConnection = { _, _ -> }, - findModelChat = { null }, - findModelMember = { null }, - setReaction = { _, _, _, _ -> }, - showItemDetails = { _, _ -> }, - developerTools = false, - showViaProxy = false - ) - } +fun PreviewChatItemView( + chatItem: ChatItem = ChatItem.getSampleData(1, CIDirection.DirectSnd(), Clock.System.now(), "hello") +) { + ChatItemView( + rhId = null, + ChatInfo.Direct.sampleData, + chatItem, + useLinkPreviews = true, + linkMode = SimplexLinkMode.DESCRIPTION, + composeState = remember { mutableStateOf(ComposeState(useLinkPreviews = true)) }, + revealed = remember { mutableStateOf(false) }, + range = 0..1, + deleteMessage = { _, _ -> }, + deleteMessages = { _ -> }, + receiveFile = { _ -> }, + cancelFile = {}, + joinGroup = { _, _ -> }, + acceptCall = { _ -> }, + scrollToItem = {}, + acceptFeature = { _, _, _ -> }, + openDirectChat = { _ -> }, + forwardItem = { _, _ -> }, + updateContactStats = { }, + updateMemberStats = { _, _ -> }, + syncContactConnection = { }, + syncMemberConnection = { _, _ -> }, + findModelChat = { null }, + findModelMember = { null }, + setReaction = { _, _, _, _ -> }, + showItemDetails = { _, _ -> }, + developerTools = false, + showViaProxy = false, + preview = true, + ) } @Preview @@ -877,6 +878,7 @@ fun PreviewChatItemViewDeletedContent() { scrollToItem = {}, acceptFeature = { _, _, _ -> }, openDirectChat = { _ -> }, + forwardItem = { _, _ -> }, updateContactStats = { }, updateMemberStats = { _, _ -> }, syncContactConnection = { }, @@ -886,7 +888,8 @@ fun PreviewChatItemViewDeletedContent() { setReaction = { _, _, _, _ -> }, showItemDetails = { _, _ -> }, developerTools = false, - showViaProxy = false + showViaProxy = false, + preview = true, ) } } diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/DeletedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/DeletedItemView.kt index 644c1997c4..9b7db099b6 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/DeletedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/DeletedItemView.kt @@ -18,8 +18,8 @@ import chat.simplex.common.ui.theme.* @Composable fun DeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, showViaProxy: Boolean) { val sent = ci.chatDir.sent - val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage - val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage + val sentColor = MaterialTheme.appColors.sentMessage + val receivedColor = MaterialTheme.appColors.receivedMessage Surface( shape = RoundedCornerShape(18.dp), color = if (sent) sentColor else receivedColor, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt index 4dc0fd9cf5..8969ca9b21 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/FramedItemView.kt @@ -46,9 +46,6 @@ fun FramedItemView( return if (chatInfo is ChatInfo.Group) chatInfo.groupInfo.membership else null } - @Composable - fun Color.toQuote(): Color = if (isInDarkTheme()) lighter(0.12f) else darker(0.12f) - @Composable fun ciQuotedMsgTextView(qi: CIQuote, lines: Int) { MarkdownText( @@ -89,11 +86,11 @@ fun FramedItemView( @Composable fun FramedItemHeader(caption: String, italic: Boolean, icon: Painter? = null, pad: Boolean = false) { - val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage - val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage + val sentColor = MaterialTheme.appColors.sentQuote + val receivedColor = MaterialTheme.appColors.receivedQuote Row( Modifier - .background(if (sent) sentColor.toQuote() else receivedColor.toQuote()) + .background(if (sent) sentColor else receivedColor) .fillMaxWidth() .padding(start = 8.dp, top = 6.dp, end = 12.dp, bottom = if (pad || (ci.quotedItem == null && ci.meta.itemForwarded == null)) 6.dp else 0.dp), horizontalArrangement = Arrangement.spacedBy(4.dp), @@ -122,11 +119,11 @@ fun FramedItemView( @Composable fun ciQuoteView(qi: CIQuote) { - val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage - val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage + val sentColor = MaterialTheme.appColors.sentQuote + val receivedColor = MaterialTheme.appColors.receivedQuote Row( Modifier - .background(if (sent) sentColor.toQuote() else receivedColor.toQuote()) + .background(if (sent) sentColor else receivedColor) .fillMaxWidth() .combinedClickable( onLongClick = { showMenu.value = true }, @@ -188,10 +185,17 @@ fun FramedItemView( val transparentBackground = (ci.content.msgContent is MsgContent.MCImage || ci.content.msgContent is MsgContent.MCVideo) && !ci.meta.isLive && ci.content.text.isEmpty() && ci.quotedItem == null && ci.meta.itemForwarded == null - val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage - val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage + val sentColor = MaterialTheme.appColors.sentMessage + val receivedColor = MaterialTheme.appColors.receivedMessage Box(Modifier .clip(RoundedCornerShape(18.dp)) + .background( + when { + transparentBackground -> Color.Transparent + sent -> MaterialTheme.colors.background + else -> MaterialTheme.colors.background + } + ) .background( when { transparentBackground -> Color.Transparent diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/IntegrityErrorItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/IntegrityErrorItemView.kt index bd2aaf140c..dc585358c4 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/IntegrityErrorItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/IntegrityErrorItemView.kt @@ -17,8 +17,7 @@ import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp import chat.simplex.common.model.ChatItem import chat.simplex.common.model.MsgErrorType -import chat.simplex.common.ui.theme.CurrentColors -import chat.simplex.common.ui.theme.SimpleXTheme +import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.AlertManager import chat.simplex.common.views.helpers.generalGetString import chat.simplex.res.MR @@ -51,7 +50,7 @@ fun IntegrityErrorItemView(msgError: MsgErrorType, ci: ChatItem, timedMessagesTT @Composable fun CIMsgError(ci: ChatItem, timedMessagesTTL: Int?, onClick: () -> Unit) { - val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage + val receivedColor = MaterialTheme.appColors.receivedMessage Surface( Modifier.clickable(onClick = onClick), shape = RoundedCornerShape(18.dp), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt index 6ecec47b6f..5b5438d76f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/MarkedDeletedItemView.kt @@ -21,8 +21,8 @@ import kotlinx.datetime.Clock @Composable fun MarkedDeletedItemView(ci: ChatItem, timedMessagesTTL: Int?, revealed: MutableState, showViaProxy: Boolean) { - val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage - val receivedColor = CurrentColors.collectAsState().value.appColors.receivedMessage + val sentColor = MaterialTheme.appColors.sentMessage + val receivedColor = MaterialTheme.appColors.receivedMessage Surface( shape = RoundedCornerShape(18.dp), color = if (ci.chatDir.sent) sentColor else receivedColor, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt index 343fc47f76..c0e222d7d1 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/item/TextItemView.kt @@ -78,7 +78,7 @@ fun MarkdownText ( val reserve = if (textLayoutDirection != LocalLayoutDirection.current && meta != null) { "\n" } else if (meta != null) { - reserveSpaceForMeta(meta, chatTTL, null, showViaProxy = showViaProxy) + reserveSpaceForMeta(meta, chatTTL, null, secondaryColor = MaterialTheme.colors.secondary, showViaProxy = showViaProxy) } else { " " } 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 40dfbeac73..8549f6abe2 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 @@ -486,6 +486,8 @@ fun deleteChatDatabaseFilesAndState() { tmpDir.deleteRecursively() getMigrationTempFilesDirectory().deleteRecursively() tmpDir.mkdir() + wallpapersDir.deleteRecursively() + wallpapersDir.mkdirs() DatabaseUtils.ksDatabasePassword.remove() controller.appPrefs.storeDBPassphrase.set(true) controller.ctrl = null @@ -535,6 +537,7 @@ suspend fun exportChatArchive( if (!m.chatDbChanged.value) { controller.apiSaveAppSettings(AppSettings.current.prepareForExport()) } + wallpapersDir.mkdirs() m.controller.apiExportArchive(config) if (storagePath == null) { deleteOldArchive(m) @@ -590,6 +593,7 @@ private fun importArchive( withLongRunningApi { try { m.controller.apiDeleteStorage() + wallpapersDir.mkdirs() try { val config = ArchiveConfig(archivePath, parentTempDirectory = databaseExportDir.toString()) val archiveErrors = m.controller.apiImportArchive(config) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt index 34d916781b..3d57f0f8b7 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatInfoImage.kt @@ -2,12 +2,14 @@ package chat.simplex.common.views.helpers 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.* import androidx.compose.material.Icon import androidx.compose.material.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.geometry.Size @@ -51,7 +53,8 @@ fun ProfileImage( size: Dp, image: String? = null, icon: ImageResource = MR.images.ic_account_circle_filled, - color: Color = MaterialTheme.colors.secondaryVariant + color: Color = MaterialTheme.colors.secondaryVariant, + backgroundColor: Color? = null ) { Box(Modifier.size(size)) { if (image == null) { @@ -61,6 +64,9 @@ fun ProfileImage( else -> null } if (iconToReplace != null) { + if (backgroundColor != null) { + Box(Modifier.size(size * 0.7f).align(Alignment.Center).background(backgroundColor, CircleShape)) + } Icon( iconToReplace, contentDescription = stringResource(MR.strings.icon_descr_profile_image_placeholder), diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt new file mode 100644 index 0000000000..8921685cd6 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt @@ -0,0 +1,447 @@ +package chat.simplex.common.views.helpers + +import androidx.compose.ui.draw.CacheDrawScope +import androidx.compose.ui.draw.DrawResult +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Size +import androidx.compose.ui.graphics.* +import androidx.compose.ui.graphics.drawscope.* +import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.unit.* +import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.ui.theme.ThemeManager.colorFromReadableHex +import chat.simplex.res.MR +import dev.icerock.moko.resources.ImageResource +import dev.icerock.moko.resources.StringResource +import kotlinx.serialization.SerialName +import kotlinx.serialization.Serializable +import java.io.File +import kotlin.math.* + +enum class PresetWallpaper( + val res: ImageResource, + val filename: String, + val scale: Float, + val background: Map, + val tint: Map, + val colors: Map, +) { + CATS(MR.images.wallpaper_cats, "cats", 0.63f, + wallpaperBackgrounds(light = "#ffF8F6EA"), + tint = mapOf( + DefaultTheme.LIGHT to "#ffefdca6".colorFromReadableHex(), + DefaultTheme.DARK to "#ff4b3b0e".colorFromReadableHex(), + DefaultTheme.SIMPLEX to "#ff51400f".colorFromReadableHex(), + DefaultTheme.BLACK to "#ff4b3b0e".colorFromReadableHex() + ), + mapOf( + DefaultTheme.LIGHT to ThemeColors( + sentMessage = "#fffffaed", + sentQuote = "#fffaf0d6", + receivedMessage = "#ffF8F7F4", + receivedQuote = "#ffefede9", + ), + DefaultTheme.DARK to ThemeColors( + sentMessage = "#ff2f2919", + sentQuote = "#ff473a1d", + receivedMessage = "#ff272624", + receivedQuote = "#ff373633", + ), + DefaultTheme.SIMPLEX to ThemeColors( + sentMessage = "#ff41371b", + sentQuote = "#ff654f1c", + receivedMessage = "#ff272624", + receivedQuote = "#ff373633", + ), + DefaultTheme.BLACK to ThemeColors( + sentMessage = "#ff41371b", + sentQuote = "#ff654f1c", + receivedMessage = "#ff1f1e1b", + receivedQuote = "#ff2f2d27", + ), + ) + ), + FLOWERS(MR.images.wallpaper_flowers, "flowers", 0.53f, + wallpaperBackgrounds(light = "#ffE2FFE4"), + tint = mapOf( + DefaultTheme.LIGHT to "#ff9CEA59".colorFromReadableHex(), + DefaultTheme.DARK to "#ff31560D".colorFromReadableHex(), + DefaultTheme.SIMPLEX to "#ff36600f".colorFromReadableHex(), + DefaultTheme.BLACK to "#ff31560D".colorFromReadableHex() + ), + mapOf( + DefaultTheme.LIGHT to ThemeColors( + sentMessage = "#fff1ffe5", + sentQuote = "#ffdcf9c4", + receivedMessage = "#ffF4F8F2", + receivedQuote = "#ffe7ece7", + ), + DefaultTheme.DARK to ThemeColors( + sentMessage = "#ff163521", + sentQuote = "#ff1B5330", + receivedMessage = "#ff242523", + receivedQuote = "#ff353733", + ), + DefaultTheme.SIMPLEX to ThemeColors( + sentMessage = "#ff184739", + sentQuote = "#ff1F6F4B", + receivedMessage = "#ff242523", + receivedQuote = "#ff353733", + ), + DefaultTheme.BLACK to ThemeColors( + sentMessage = "#ff184739", + sentQuote = "#ff1F6F4B", + receivedMessage = "#ff1c1f1a", + receivedQuote = "#ff282b25", + ), + ) + ), + HEARTS(MR.images.wallpaper_hearts, "hearts", 0.59f, + wallpaperBackgrounds(light = "#ffFDECEC"), + tint = mapOf( + DefaultTheme.LIGHT to "#fffde0e0".colorFromReadableHex(), + DefaultTheme.DARK to "#ff3c0f0f".colorFromReadableHex(), + DefaultTheme.SIMPLEX to "#ff411010".colorFromReadableHex(), + DefaultTheme.BLACK to "#ff3C0F0F".colorFromReadableHex() + ), + mapOf( + DefaultTheme.LIGHT to ThemeColors( + sentMessage = "#fffff4f4", + sentQuote = "#ffffdfdf", + receivedMessage = "#fff8f6f6", + receivedQuote = "#ffefebeb", + ), + DefaultTheme.DARK to ThemeColors( + sentMessage = "#ff301515", + sentQuote = "#ff4C1818", + receivedMessage = "#ff242121", + receivedQuote = "#ff3b3535", + ), + DefaultTheme.SIMPLEX to ThemeColors( + sentMessage = "#ff491A28", + sentQuote = "#ff761F29", + receivedMessage = "#ff242121", + receivedQuote = "#ff3b3535", + ), + DefaultTheme.BLACK to ThemeColors( + sentMessage = "#ff491A28", + sentQuote = "#ff761F29", + receivedMessage = "#ff1f1b1b", + receivedQuote = "#ff2e2626", + ), + ) + ), + KIDS(MR.images.wallpaper_kids, "kids", 0.53f, + wallpaperBackgrounds(light = "#ffdbfdfb"), + tint = mapOf( + DefaultTheme.LIGHT to "#ffadeffc".colorFromReadableHex(), + DefaultTheme.DARK to "#ff16404B".colorFromReadableHex(), + DefaultTheme.SIMPLEX to "#ff184753".colorFromReadableHex(), + DefaultTheme.BLACK to "#ff16404B".colorFromReadableHex() + ), + mapOf( + DefaultTheme.LIGHT to ThemeColors( + sentMessage = "#ffeafeff", + sentQuote = "#ffcbf4f7", + receivedMessage = "#fff3fafa", + receivedQuote = "#ffe4efef", + ), + DefaultTheme.DARK to ThemeColors( + sentMessage = "#ff16302F", + sentQuote = "#ff1a4a49", + receivedMessage = "#ff252626", + receivedQuote = "#ff373A39", + ), + DefaultTheme.SIMPLEX to ThemeColors( + sentMessage = "#ff1a4745", + sentQuote = "#ff1d6b69", + receivedMessage = "#ff252626", + receivedQuote = "#ff373a39", + ), + DefaultTheme.BLACK to ThemeColors( + sentMessage = "#ff1a4745", + sentQuote = "#ff1d6b69", + receivedMessage = "#ff1e1f1f", + receivedQuote = "#ff262b29", + ), + ) + ), + SCHOOL(MR.images.wallpaper_school, "school", 0.53f, + wallpaperBackgrounds(light = "#ffE7F5FF"), + tint = mapOf( + DefaultTheme.LIGHT to "#ffCEEBFF".colorFromReadableHex(), + DefaultTheme.DARK to "#ff0F293B".colorFromReadableHex(), + DefaultTheme.SIMPLEX to "#ff112f43".colorFromReadableHex(), + DefaultTheme.BLACK to "#ff0F293B".colorFromReadableHex() + ), + mapOf( + DefaultTheme.LIGHT to ThemeColors( + sentMessage = "#ffeef9ff", + sentQuote = "#ffD6EDFA", + receivedMessage = "#ffF3F5F9", + receivedQuote = "#ffe4e8ee", + ), + DefaultTheme.DARK to ThemeColors( + sentMessage = "#ff172833", + sentQuote = "#ff1C3E4F", + receivedMessage = "#ff26282c", + receivedQuote = "#ff393c40", + ), + DefaultTheme.SIMPLEX to ThemeColors( + sentMessage = "#ff1A3C5D", + sentQuote = "#ff235b80", + receivedMessage = "#ff26282c", + receivedQuote = "#ff393c40", + ), + DefaultTheme.BLACK to ThemeColors( + sentMessage = "#ff1A3C5D", + sentQuote = "#ff235b80", + receivedMessage = "#ff1d1e22", + receivedQuote = "#ff292b2f", + ), + ) + ), + TRAVEL(MR.images.wallpaper_travel, "travel", 0.68f, + wallpaperBackgrounds(light = "#fff9eeff"), + tint = mapOf( + DefaultTheme.LIGHT to "#ffeedbfe".colorFromReadableHex(), + DefaultTheme.DARK to "#ff311E48".colorFromReadableHex(), + DefaultTheme.SIMPLEX to "#ff35204e".colorFromReadableHex(), + DefaultTheme.BLACK to "#ff311E48".colorFromReadableHex() + ), + mapOf( + DefaultTheme.LIGHT to ThemeColors( + sentMessage = "#fffcf6ff", + sentQuote = "#fff2e0fc", + receivedMessage = "#ffF6F4F7", + receivedQuote = "#ffede9ee", + ), + DefaultTheme.DARK to ThemeColors( + sentMessage = "#ff33263B", + sentQuote = "#ff53385E", + receivedMessage = "#ff272528", + receivedQuote = "#ff3B373E", + ), + DefaultTheme.SIMPLEX to ThemeColors( + sentMessage = "#ff3C255D", + sentQuote = "#ff623485", + receivedMessage = "#ff26273B", + receivedQuote = "#ff3A394F", + ), + DefaultTheme.BLACK to ThemeColors( + sentMessage = "#ff3C255D", + sentQuote = "#ff623485", + receivedMessage = "#ff231f23", + receivedQuote = "#ff2c2931", + ), + ) + ); + + fun toType(base: DefaultTheme, scale: Float? = null): WallpaperType = + WallpaperType.Preset( + filename, + scale ?: appPrefs.themeOverrides.get().firstOrNull { it.wallpaper != null && it.wallpaper.preset == filename && it.base == base }?.wallpaper?.scale ?: 1f + ) + + companion object { + fun from(filename: String): PresetWallpaper? = + entries.firstOrNull { it.filename == filename } + } +} + +fun wallpaperBackgrounds(light: String): Map = + mapOf( + DefaultTheme.LIGHT to light.colorFromReadableHex(), + DefaultTheme.DARK to "#ff121212".colorFromReadableHex(), + DefaultTheme.SIMPLEX to "#ff111528".colorFromReadableHex(), + DefaultTheme.BLACK to "#ff070707".colorFromReadableHex() + ) + +@Serializable +enum class WallpaperScaleType(val contentScale: ContentScale, val text: StringResource) { + @SerialName("fill") FILL(ContentScale.Crop, MR.strings.wallpaper_scale_fill), + @SerialName("fit") FIT(ContentScale.Fit, MR.strings.wallpaper_scale_fit), + @SerialName("repeat") REPEAT(ContentScale.Fit, MR.strings.wallpaper_scale_repeat), +} + +sealed class WallpaperType { + abstract val scale: Float? + + val image by lazy { + val filename = when (this) { + is Preset -> filename + is Image -> filename + else -> return@lazy null + } + if (filename == "") return@lazy null + if (cachedImages[filename] != null) { + cachedImages[filename] + } else { + val res = if (this is Preset) { + (PresetWallpaper.from(filename) ?: PresetWallpaper.CATS).res.toComposeImageBitmap()!! + } else { + try { + // In case of unintentional image deletion don't crash the app + File(getWallpaperFilePath(filename)).inputStream().use { loadImageBitmap(it) } + } catch (e: Exception) { + Log.e(TAG, "Error while loading wallpaper file: ${e.stackTraceToString()}") + null + } + } + res?.prepareToDraw() + cachedImages[filename] = res ?: return@lazy null + res + } + } + + fun sameType(other: WallpaperType?): Boolean = + if (this is Preset && other is Preset) this.filename == other.filename + else this.javaClass == other?.javaClass + + fun samePreset(other: PresetWallpaper?): Boolean = this is Preset && filename == other?.filename + + data class Preset( + val filename: String, + override val scale: Float?, + ): WallpaperType() { + val predefinedImageScale = PresetWallpaper.from(filename)?.scale ?: 1f + } + + data class Image( + val filename: String, + override val scale: Float?, + val scaleType: WallpaperScaleType?, + ): WallpaperType() + + object Empty: WallpaperType() { + override val scale: Float? + get() = null + } + + fun defaultBackgroundColor(theme: DefaultTheme, materialBackground: Color): Color = + if (this is Preset) { + (PresetWallpaper.from(filename) ?: PresetWallpaper.CATS).background[theme]!! + } else { + materialBackground + } + + fun defaultTintColor(theme: DefaultTheme): Color = + if (this is Preset) { + (PresetWallpaper.from(filename) ?: PresetWallpaper.CATS).tint[theme]!! + } else if (this is Image && scaleType == WallpaperScaleType.REPEAT) { + Color.Transparent + } else { + Color.Transparent + } + + companion object { + var cachedImages: MutableMap = mutableMapOf() + + fun from(wallpaper: ThemeWallpaper?): WallpaperType? { + return if (wallpaper == null) { + null + } else if (wallpaper.preset != null) { + Preset(wallpaper.preset, wallpaper.scale) + } else if (wallpaper.imageFile != null) { + Image(wallpaper.imageFile, wallpaper.scale, wallpaper.scaleType) + } else { + Empty + } + } + } +} + +private fun drawToBitmap(image: ImageBitmap, imageScale: Float, tint: Color, size: Size, density: Float, layoutDirection: LayoutDirection): ImageBitmap { + val quality = if (appPlatform.isAndroid) FilterQuality.High else FilterQuality.Low + val drawScope = CanvasDrawScope() + val bitmap = ImageBitmap(size.width.toInt(), size.height.toInt()) + val canvas = Canvas(bitmap) + drawScope.draw( + density = Density(density), + layoutDirection = layoutDirection, + canvas = canvas, + size = size, + ) { + val scale = imageScale * density + for (h in 0..(size.height / image.height / scale).roundToInt()) { + for (w in 0..(size.width / image.width / scale).roundToInt()) { + drawImage( + image, + dstOffset = IntOffset(x = (w * image.width * scale).roundToInt(), y = (h * image.height * scale).roundToInt()), + dstSize = IntSize((image.width * scale).roundToInt(), (image.height * scale).roundToInt()), + colorFilter = ColorFilter.tint(tint, BlendMode.SrcIn), + filterQuality = quality + ) + } + } + } + return bitmap +} + +fun CacheDrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperType, background: Color, tint: Color): DrawResult { + val imageScale = if (imageType is WallpaperType.Preset) { + (imageType.scale ?: 1f) * imageType.predefinedImageScale + } else if (imageType is WallpaperType.Image && imageType.scaleType == WallpaperScaleType.REPEAT) { + imageType.scale ?: 1f + } else { + 1f + } + val image = if (imageType is WallpaperType.Preset || (imageType is WallpaperType.Image && imageType.scaleType == WallpaperScaleType.REPEAT)) { + drawToBitmap(image, imageScale, tint, size, density, layoutDirection) + } else { + image + } + + return onDrawBehind { + val quality = if (appPlatform.isAndroid) FilterQuality.High else FilterQuality.Low + drawRect(background) + when (imageType) { + is WallpaperType.Preset -> drawImage(image) + is WallpaperType.Image -> when (val scaleType = imageType.scaleType ?: WallpaperScaleType.FILL) { + WallpaperScaleType.REPEAT -> drawImage(image) + WallpaperScaleType.FILL, WallpaperScaleType.FIT -> { + clipRect { + val scale = scaleType.contentScale.computeScaleFactor(Size(image.width.toFloat(), image.height.toFloat()), Size(size.width, size.height)) + val scaledWidth = (image.width * scale.scaleX).roundToInt() + val scaledHeight = (image.height * scale.scaleY).roundToInt() + // Large image will cause freeze + if (image.width > 4320 || image.height > 4320) return@clipRect + + drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + if (scaleType == WallpaperScaleType.FIT) { + if (scaledWidth < size.width) { + // has black lines at left and right sides + var x = (size.width - scaledWidth) / 2 + while (x > 0) { + drawImage(image, dstOffset = IntOffset(x = (x - scaledWidth).roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + x -= scaledWidth + } + x = size.width - (size.width - scaledWidth) / 2 + while (x < size.width) { + drawImage(image, dstOffset = IntOffset(x = x.roundToInt(), y = ((size.height - scaledHeight) / 2).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + x += scaledWidth + } + } else { + // has black lines at top and bottom sides + var y = (size.height - scaledHeight) / 2 + while (y > 0) { + drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = (y - scaledHeight).roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + y -= scaledHeight + } + y = size.height - (size.height - scaledHeight) / 2 + while (y < size.height) { + drawImage(image, dstOffset = IntOffset(x = ((size.width - scaledWidth) / 2).roundToInt(), y = y.roundToInt()), dstSize = IntSize(scaledWidth, scaledHeight), filterQuality = quality) + y += scaledHeight + } + } + } + } + drawRect(tint) + } + } + is WallpaperType.Empty -> {} + } + } +} 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 2fb27e29b1..b26a047b0e 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 @@ -51,7 +51,7 @@ fun CloseSheetBar(close: (() -> Unit)?, showClose: Boolean = true, tintColor: Co @Composable fun AppBarTitle(title: String, hostDevice: Pair? = null, withPadding: Boolean = true, bottomPadding: Dp = DEFAULT_PADDING * 1.5f) { val theme = CurrentColors.collectAsState() - val titleColor = CurrentColors.collectAsState().value.appColors.title + val titleColor = MaterialTheme.appColors.title val brush = if (theme.value.base == DefaultTheme.SIMPLEX) Brush.linearGradient(listOf(titleColor.darker(0.2f), titleColor.lighter(0.35f)), Offset(0f, Float.POSITIVE_INFINITY), Offset(Float.POSITIVE_INFINITY, 0f)) else // color is not updated when changing themes if I pass null here diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt index 93d0a56766..20b8f7c09f 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/LinkPreviews.kt @@ -76,7 +76,7 @@ suspend fun getLinkPreview(url: String): LinkPreview? { @Composable fun ComposeLinkView(linkPreview: LinkPreview?, cancelPreview: () -> Unit, cancelEnabled: Boolean) { - val sentColor = CurrentColors.collectAsState().value.appColors.sentMessage + val sentColor = MaterialTheme.appColors.sentMessage Row( Modifier.fillMaxWidth().padding(top = 8.dp).background(sentColor), verticalAlignment = Alignment.CenterVertically 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 887a5bfdd9..6ee60c4596 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 @@ -41,9 +41,12 @@ enum class ModalPlacement { } class ModalData { - private val state = mutableMapOf>() + private val state = mutableMapOf>() fun stateGetOrPut (key: String, default: () -> T): MutableState = state.getOrPut(key) { mutableStateOf(default() as Any) } as MutableState + + fun stateGetOrPutNullable (key: String, default: () -> T?): MutableState = + state.getOrPut(key) { mutableStateOf(default() as Any?) } as MutableState } class ModalManager(private val placement: ModalPlacement? = null) { 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 c96a277fb8..94affad0e7 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 @@ -1,6 +1,5 @@ import androidx.compose.foundation.* import androidx.compose.foundation.layout.* -import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment @@ -13,12 +12,15 @@ import androidx.compose.ui.text.AnnotatedString import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.* +import chat.simplex.common.model.NotificationsMode import chat.simplex.common.platform.onRightClick import chat.simplex.common.platform.windowWidth import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* +import chat.simplex.common.views.onboarding.SelectableCard import chat.simplex.common.views.usersettings.SettingsActionItemWithContent import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.stringResource @Composable fun SectionView(title: String? = null, padding: PaddingValues = PaddingValues(), content: (@Composable ColumnScope.() -> Unit)) { @@ -76,6 +78,26 @@ fun SectionViewSelectable( SectionTextFooter(values.first { it.value == currentValue.value }.description) } +@Composable +fun SectionViewSelectableCards( + title: String?, + currentValue: State, + values: List>, + onSelected: (T) -> Unit, +) { + SectionView(title) { + Column(Modifier.padding(horizontal = DEFAULT_PADDING)) { + if (title != null) { + Text(title, Modifier.fillMaxWidth(), textAlign = TextAlign.Center) + Spacer(Modifier.height(DEFAULT_PADDING * 2f)) + } + values.forEach { item -> + SelectableCard(currentValue, item.value, item.title, item.description, onSelected) + } + } + } +} + @Composable fun SectionItemView( click: (() -> Unit)? = null, diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ThemeModeEditor.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ThemeModeEditor.kt new file mode 100644 index 0000000000..24f9bf539b --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ThemeModeEditor.kt @@ -0,0 +1,520 @@ +package chat.simplex.common.views.helpers + +import SectionBottomSpacer +import SectionItemView +import SectionSpacer +import SectionView +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.material.MaterialTheme +import androidx.compose.material.MaterialTheme.colors +import androidx.compose.material.Text +import androidx.compose.runtime.* +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import chat.simplex.common.model.ChatController.appPrefs +import chat.simplex.common.model.yaml +import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.* +import chat.simplex.common.views.usersettings.* +import chat.simplex.common.views.usersettings.AppearanceScope.WallpaperPresetSelector +import chat.simplex.common.views.usersettings.AppearanceScope.editColor +import chat.simplex.res.MR +import dev.icerock.moko.resources.compose.painterResource +import dev.icerock.moko.resources.compose.stringResource +import kotlinx.serialization.encodeToString +import java.net.URI + +@Composable +fun ModalData.UserWallpaperEditor( + theme: ThemeModeOverride, + applyToMode: DefaultThemeMode?, + globalThemeUsed: MutableState, + save: suspend (applyToMode: DefaultThemeMode?, ThemeModeOverride?) -> Unit +) { + ColumnWithScrollBar( + Modifier + .fillMaxSize() + ) { + val applyToMode = remember { stateGetOrPutNullable("applyToMode") { applyToMode } } + var showMore by remember { stateGetOrPut("showMore") { false } } + val themeModeOverride = remember { stateGetOrPut("themeModeOverride") { theme } } + val currentTheme by CurrentColors.collectAsState() + + AppBarTitle(stringResource(MR.strings.settings_section_title_user_theme)) + val wallpaperImage = MaterialTheme.wallpaper.type.image + val wallpaperType = MaterialTheme.wallpaper.type + + val onTypeCopyFromSameTheme = { type: WallpaperType? -> + if (type is WallpaperType.Image && chatModel.remoteHostId() != null) { + false + } else { + ThemeManager.copyFromSameThemeOverrides(type, null, themeModeOverride) + withBGApi { save(applyToMode.value, themeModeOverride.value) } + globalThemeUsed.value = false + true + } + } + val preApplyGlobalIfNeeded = { type: WallpaperType? -> + if (globalThemeUsed.value) { + onTypeCopyFromSameTheme(type) + } + } + val onTypeChange: (WallpaperType?) -> Unit = { type: WallpaperType? -> + if (globalThemeUsed.value) { + preApplyGlobalIfNeeded(type) + // Saves copied static image instead of original from global theme + ThemeManager.applyWallpaper(themeModeOverride.value.type, themeModeOverride) + } else { + ThemeManager.applyWallpaper(type, themeModeOverride) + } + withBGApi { save(applyToMode.value, themeModeOverride.value) } + } + + val importWallpaperLauncher = rememberFileChooserLauncher(true) { to: URI? -> + if (to != null) { + val filename = saveWallpaperFile(to) + if (filename != null) { + onTypeChange(WallpaperType.Image(filename, 1f, WallpaperScaleType.FILL)) + } + } + } + + val currentColors = { type: WallpaperType? -> + // If applying for : + // - all themes: no overrides needed + // - specific user: only user overrides for currently selected theme are needed, because they will NOT be copied when other wallpaper is selected + val perUserOverride = if (wallpaperType.sameType(type)) chatModel.currentUser.value?.uiThemes else null + ThemeManager.currentColors(type, null, perUserOverride, appPrefs.themeOverrides.get()) + } + val onChooseType: (WallpaperType?) -> Unit = { type: WallpaperType? -> + when { + // don't have image in parent or already selected wallpaper with custom image + type is WallpaperType.Image && chatModel.remoteHostId() != null -> { /* do nothing */ } + type is WallpaperType.Image && (wallpaperType is WallpaperType.Image || currentColors(type).wallpaper.type.image == null) -> withLongRunningApi { importWallpaperLauncher.launch("image/*") } + type is WallpaperType.Image -> onTypeCopyFromSameTheme(currentColors(type).wallpaper.type) + themeModeOverride.value.type != type || currentTheme.wallpaper.type != type -> onTypeCopyFromSameTheme(type) + else -> onTypeChange(type) + } + } + + val editColor = { name: ThemeColor -> + editColor( + name, + wallpaperType, + wallpaperImage, + onColorChange = { color -> + preApplyGlobalIfNeeded(themeModeOverride.value.type) + ThemeManager.applyThemeColor(name, color, themeModeOverride) + withBGApi { save(applyToMode.value, themeModeOverride.value) } + } + ) + } + + WallpaperPresetSelector( + selectedWallpaper = wallpaperType, + baseTheme = currentTheme.base, + currentColors = { type -> + // If applying for : + // - all themes: no overrides needed + // - specific user: only user overrides for currently selected theme are needed, because they will NOT be copied when other wallpaper is selected + val perUserOverride = if (wallpaperType.sameType(type)) chatModel.currentUser.value?.uiThemes else null + ThemeManager.currentColors(type, null, perUserOverride, appPrefs.themeOverrides.get()) + }, + onChooseType = onChooseType + ) + + WallpaperSetupView( + themeModeOverride.value.type, + CurrentColors.collectAsState().value.base, + currentTheme.wallpaper, + currentTheme.appColors.sentMessage, + currentTheme.appColors.sentQuote, + currentTheme.appColors.receivedMessage, + currentTheme.appColors.receivedQuote, + editColor = { name -> editColor(name) }, + onTypeChange = onTypeChange, + ) + + SectionSpacer() + + if (!globalThemeUsed.value) { + ResetToGlobalThemeButton(true) { + themeModeOverride.value = ThemeManager.defaultActiveTheme(chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) + globalThemeUsed.value = true + withBGApi { save(applyToMode.value, null) } + } + } + + SetDefaultThemeButton { + globalThemeUsed.value = false + val lightBase = DefaultTheme.LIGHT + val darkBase = if (CurrentColors.value.base != DefaultTheme.LIGHT) CurrentColors.value.base else if (appPrefs.systemDarkTheme.get() == DefaultTheme.DARK.themeName) DefaultTheme.DARK else if (appPrefs.systemDarkTheme.get() == DefaultTheme.BLACK.themeName) DefaultTheme.BLACK else DefaultTheme.SIMPLEX + val mode = themeModeOverride.value.mode + withBGApi { + // Saving for both modes in one place by changing mode once per save + if (applyToMode.value == null) { + val oppositeMode = if (mode == DefaultThemeMode.LIGHT) DefaultThemeMode.DARK else DefaultThemeMode.LIGHT + save(oppositeMode, ThemeModeOverride.withFilledAppDefaults(oppositeMode, if (oppositeMode == DefaultThemeMode.LIGHT) lightBase else darkBase)) + } + themeModeOverride.value = ThemeModeOverride.withFilledAppDefaults(mode, if (mode == DefaultThemeMode.LIGHT) lightBase else darkBase) + save(themeModeOverride.value.mode, themeModeOverride.value) + } + } + + KeyChangeEffect(theme.mode) { + themeModeOverride.value = theme + if (applyToMode.value != null) { + applyToMode.value = theme.mode + } + } + + // Applies updated global theme if current one tracks global theme + KeyChangeEffect(CurrentColors.collectAsState().value) { + if (globalThemeUsed.value) { + themeModeOverride.value = ThemeManager.defaultActiveTheme(chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) + globalThemeUsed.value = true + } + } + + SectionSpacer() + + if (showMore) { + val values by remember { mutableStateOf( + listOf( + null to generalGetString(MR.strings.chat_theme_apply_to_all_modes), + DefaultThemeMode.LIGHT to generalGetString(MR.strings.chat_theme_apply_to_light_mode), + DefaultThemeMode.DARK to generalGetString(MR.strings.chat_theme_apply_to_dark_mode), + ) + ) + } + ExposedDropDownSettingRow( + generalGetString(MR.strings.chat_theme_apply_to_mode), + values, + applyToMode, + icon = null, + enabled = remember { mutableStateOf(true) }, + onSelected = { + applyToMode.value = it + if (it != null && it != CurrentColors.value.base.mode) { + val lightBase = DefaultTheme.LIGHT + val darkBase = if (CurrentColors.value.base != DefaultTheme.LIGHT) CurrentColors.value.base else if (appPrefs.systemDarkTheme.get() == DefaultTheme.DARK.themeName) DefaultTheme.DARK else if (appPrefs.systemDarkTheme.get() == DefaultTheme.BLACK.themeName) DefaultTheme.BLACK else DefaultTheme.SIMPLEX + ThemeManager.applyTheme(if (it == DefaultThemeMode.LIGHT) lightBase.themeName else darkBase.themeName) + } + } + ) + + SectionSpacer() + + AppearanceScope.CustomizeThemeColorsSection(currentTheme, editColor = editColor) + + SectionSpacer() + ImportExportThemeSection(null, remember { chatModel.currentUser }.value?.uiThemes) { + withBGApi { + themeModeOverride.value = it + save(applyToMode.value, it) + } + } + } else { + AdvancedSettingsButton { showMore = true } + } + + SectionBottomSpacer() + } +} + +@Composable +fun ModalData.ChatWallpaperEditor( + theme: ThemeModeOverride, + applyToMode: DefaultThemeMode?, + globalThemeUsed: MutableState, + save: suspend (applyToMode: DefaultThemeMode?, ThemeModeOverride?) -> Unit +) { + ColumnWithScrollBar( + Modifier + .fillMaxSize() + ) { + val applyToMode = remember { stateGetOrPutNullable("applyToMode") { applyToMode } } + var showMore by remember { stateGetOrPut("showMore") { false } } + val themeModeOverride = remember { stateGetOrPut("themeModeOverride") { theme } } + val currentTheme by remember(themeModeOverride.value, CurrentColors.collectAsState().value) { + mutableStateOf( + ThemeManager.currentColors(null, if (globalThemeUsed.value) null else themeModeOverride.value, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get()) + ) + } + + AppBarTitle(stringResource(MR.strings.settings_section_title_chat_theme)) + + val onTypeCopyFromSameTheme: (WallpaperType?) -> Boolean = { type -> + if (type is WallpaperType.Image && chatModel.remoteHostId() != null) { + false + } else { + val success = ThemeManager.copyFromSameThemeOverrides(type, chatModel.currentUser.value?.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight), themeModeOverride) + if (success) { + withBGApi { save(applyToMode.value, themeModeOverride.value) } + globalThemeUsed.value = false + } + success + } + } + val preApplyGlobalIfNeeded = { type: WallpaperType? -> + if (globalThemeUsed.value) { + onTypeCopyFromSameTheme(type) + } + } + val onTypeChange: (WallpaperType?) -> Unit = { type -> + if (globalThemeUsed.value) { + preApplyGlobalIfNeeded(type) + // Saves copied static image instead of original from global theme + ThemeManager.applyWallpaper(themeModeOverride.value.type, themeModeOverride) + } else { + ThemeManager.applyWallpaper(type, themeModeOverride) + } + withBGApi { save(applyToMode.value, themeModeOverride.value) } + } + + val editColor: (ThemeColor) -> Unit = { name: ThemeColor -> + ModalManager.end.showModal { + val currentTheme by remember(themeModeOverride.value, CurrentColors.collectAsState().value) { + mutableStateOf( + ThemeManager.currentColors(null, themeModeOverride.value, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get()) + ) + } + val initialColor: Color = when (name) { + ThemeColor.WALLPAPER_BACKGROUND -> currentTheme.wallpaper.background ?: Color.Transparent + ThemeColor.WALLPAPER_TINT -> currentTheme.wallpaper.tint ?: Color.Transparent + ThemeColor.PRIMARY -> currentTheme.colors.primary + ThemeColor.PRIMARY_VARIANT -> currentTheme.colors.primaryVariant + ThemeColor.SECONDARY -> currentTheme.colors.secondary + ThemeColor.SECONDARY_VARIANT -> currentTheme.colors.secondaryVariant + ThemeColor.BACKGROUND -> currentTheme.colors.background + ThemeColor.SURFACE -> currentTheme.colors.surface + ThemeColor.TITLE -> currentTheme.appColors.title + ThemeColor.PRIMARY_VARIANT2 -> currentTheme.appColors.primaryVariant2 + ThemeColor.SENT_MESSAGE -> currentTheme.appColors.sentMessage + ThemeColor.SENT_QUOTE -> currentTheme.appColors.sentQuote + ThemeColor.RECEIVED_MESSAGE -> currentTheme.appColors.receivedMessage + ThemeColor.RECEIVED_QUOTE -> currentTheme.appColors.receivedQuote + } + AppearanceScope.ColorEditor( + name, + initialColor, + CurrentColors.collectAsState().value.base, + themeModeOverride.value.type, + themeModeOverride.value.type?.image, + currentTheme.wallpaper.background, + currentTheme.wallpaper.tint, + currentColors = { + ThemeManager.currentColors(null, themeModeOverride.value, chatModel.currentUser.value?.uiThemes, appPreferences.themeOverrides.get()) + }, + onColorChange = { color -> + preApplyGlobalIfNeeded(themeModeOverride.value.type) + ThemeManager.applyThemeColor(name, color, themeModeOverride) + withBGApi { save(applyToMode.value, themeModeOverride.value) } + } + ) + } + } + + val importWallpaperLauncher = rememberFileChooserLauncher(true) { to: URI? -> + if (to != null) { + val filename = saveWallpaperFile(to) + if (filename != null) { + // Delete only non-user image + if (!globalThemeUsed.value) { + removeWallpaperFile((themeModeOverride.value.type as? WallpaperType.Image)?.filename) + } + globalThemeUsed.value = false + onTypeChange(WallpaperType.Image(filename, 1f, WallpaperScaleType.FILL)) + } + } + } + + val currentColors = { type: WallpaperType? -> + ThemeManager.currentColors(type, if (type?.sameType(themeModeOverride.value.type) == true) themeModeOverride.value else null, chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) + } + + WallpaperPresetSelector( + selectedWallpaper = currentTheme.wallpaper.type, + activeBackgroundColor = currentTheme.wallpaper.background, + activeTintColor = currentTheme.wallpaper.tint, + baseTheme = CurrentColors.collectAsState().value.base, + currentColors = { type -> currentColors(type) }, + onChooseType = { type -> + when { + type is WallpaperType.Image && chatModel.remoteHostId() != null -> { /* do nothing */ } + type is WallpaperType.Image && ((themeModeOverride.value.type is WallpaperType.Image && !globalThemeUsed.value) || currentColors(type).wallpaper.type.image == null) -> { + withLongRunningApi { importWallpaperLauncher.launch("image/*") } + } + type is WallpaperType.Image -> { + if (!onTypeCopyFromSameTheme(currentColors(type).wallpaper.type)) { + withLongRunningApi { importWallpaperLauncher.launch("image/*") } + } + } + globalThemeUsed.value || themeModeOverride.value.type != type -> { + onTypeCopyFromSameTheme(type) + } + else -> { + onTypeChange(type) + } + } + }, + ) + + WallpaperSetupView( + themeModeOverride.value.type, + CurrentColors.collectAsState().value.base, + currentTheme.wallpaper, + currentTheme.appColors.sentMessage, + currentTheme.appColors.sentQuote, + currentTheme.appColors.receivedMessage, + currentTheme.appColors.receivedQuote, + editColor = editColor, + onTypeChange = onTypeChange, + ) + + SectionSpacer() + + if (!globalThemeUsed.value) { + ResetToGlobalThemeButton(remember { chatModel.currentUser }.value?.uiThemes?.preferredMode(isInDarkTheme()) == null) { + themeModeOverride.value = ThemeManager.defaultActiveTheme(chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) + globalThemeUsed.value = true + withBGApi { save(applyToMode.value, null) } + } + } + + SetDefaultThemeButton { + globalThemeUsed.value = false + val lightBase = DefaultTheme.LIGHT + val darkBase = if (CurrentColors.value.base != DefaultTheme.LIGHT) CurrentColors.value.base else if (appPrefs.systemDarkTheme.get() == DefaultTheme.DARK.themeName) DefaultTheme.DARK else if (appPrefs.systemDarkTheme.get() == DefaultTheme.BLACK.themeName) DefaultTheme.BLACK else DefaultTheme.SIMPLEX + val mode = themeModeOverride.value.mode + withBGApi { + // Saving for both modes in one place by changing mode once per save + if (applyToMode.value == null) { + val oppositeMode = if (mode == DefaultThemeMode.LIGHT) DefaultThemeMode.DARK else DefaultThemeMode.LIGHT + save(oppositeMode, ThemeModeOverride.withFilledAppDefaults(oppositeMode, if (oppositeMode == DefaultThemeMode.LIGHT) lightBase else darkBase)) + } + themeModeOverride.value = ThemeModeOverride.withFilledAppDefaults(mode, if (mode == DefaultThemeMode.LIGHT) lightBase else darkBase) + save(themeModeOverride.value.mode, themeModeOverride.value) + } + } + + KeyChangeEffect(theme.mode) { + themeModeOverride.value = theme + if (applyToMode.value != null) { + applyToMode.value = theme.mode + } + } + + // Applies updated global theme if current one tracks global theme + KeyChangeEffect(CurrentColors.collectAsState()) { + if (globalThemeUsed.value) { + themeModeOverride.value = ThemeManager.defaultActiveTheme(chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) + globalThemeUsed.value = true + } + } + + SectionSpacer() + + if (showMore) { + val values by remember { mutableStateOf( + listOf( + null to generalGetString(MR.strings.chat_theme_apply_to_all_modes), + DefaultThemeMode.LIGHT to generalGetString(MR.strings.chat_theme_apply_to_light_mode), + DefaultThemeMode.DARK to generalGetString(MR.strings.chat_theme_apply_to_dark_mode), + ) + ) + } + ExposedDropDownSettingRow( + generalGetString(MR.strings.chat_theme_apply_to_mode), + values, + applyToMode, + icon = null, + enabled = remember { mutableStateOf(true) }, + onSelected = { + applyToMode.value = it + if (it != null && it != CurrentColors.value.base.mode) { + val lightBase = DefaultTheme.LIGHT + val darkBase = if (CurrentColors.value.base != DefaultTheme.LIGHT) CurrentColors.value.base else if (appPrefs.systemDarkTheme.get() == DefaultTheme.DARK.themeName) DefaultTheme.DARK else if (appPrefs.systemDarkTheme.get() == DefaultTheme.BLACK.themeName) DefaultTheme.BLACK else DefaultTheme.SIMPLEX + ThemeManager.applyTheme(if (it == DefaultThemeMode.LIGHT) lightBase.themeName else darkBase.themeName) + } + } + ) + + SectionSpacer() + + AppearanceScope.CustomizeThemeColorsSection(currentTheme, editColor = editColor) + + SectionSpacer() + ImportExportThemeSection(themeModeOverride.value, remember { chatModel.currentUser }.value?.uiThemes) { + withBGApi { + themeModeOverride.value = it + save(applyToMode.value, it) + } + } + } else { + AdvancedSettingsButton { showMore = true } + } + + SectionBottomSpacer() + } +} + +@Composable +private fun ImportExportThemeSection(perChat: ThemeModeOverride?, perUser: ThemeModeOverrides?, save: (ThemeModeOverride) -> Unit) { + SectionView { + val theme = remember { mutableStateOf(null as String?) } + val exportThemeLauncher = rememberFileChooserLauncher(false) { to: URI? -> + val themeValue = theme.value + if (themeValue != null && to != null) { + copyBytesToFile(themeValue.byteInputStream(), to) { + theme.value = null + } + } + } + SectionItemView({ + val overrides = ThemeManager.currentThemeOverridesForExport(perChat, perUser) + val lines = yaml.encodeToString(overrides).lines() + // Removing theme id without using custom serializer or data class + theme.value = lines.subList(1, lines.size).joinToString("\n") + withLongRunningApi { exportThemeLauncher.launch("simplex.theme") } + }) { + Text(generalGetString(MR.strings.export_theme), color = colors.primary) + } + val importThemeLauncher = rememberFileChooserLauncher(true) { to: URI? -> + if (to != null) { + val theme = getThemeFromUri(to) + if (theme != null) { + val res = ThemeModeOverride(mode = theme.base.mode, colors = theme.colors, wallpaper = theme.wallpaper?.importFromString()).removeSameColors(theme.base) + save(res) + } + } + } + // Can not limit to YAML mime type since it's unsupported by Android + SectionItemView({ withLongRunningApi { importThemeLauncher.launch("*/*") } }) { + Text(generalGetString(MR.strings.import_theme), color = colors.primary) + } + } +} + +@Composable +private fun ResetToGlobalThemeButton(app: Boolean, onClick: () -> Unit) { + SectionItemView(onClick) { + Text(stringResource(if (app) MR.strings.chat_theme_reset_to_app_theme else MR.strings.chat_theme_reset_to_user_theme), color = MaterialTheme.colors.primary) + } +} + +@Composable +private fun SetDefaultThemeButton(onClick: () -> Unit) { + SectionItemView(onClick) { + Text(stringResource(MR.strings.chat_theme_set_default_theme), color = MaterialTheme.colors.primary) + } +} + +@Composable +private fun AdvancedSettingsButton(onClick: () -> Unit) { + SettingsActionItem( + painterResource(MR.images.ic_arrow_downward), + stringResource(MR.strings.wallpaper_advanced_settings), + click = onClick + ) +} 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 9a6e3a0f9a..884551f600 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 @@ -19,6 +19,7 @@ import kotlinx.serialization.encodeToString import java.io.* import java.net.URI import java.nio.file.Files +import java.nio.file.StandardCopyOption import java.text.SimpleDateFormat import java.util.* import java.util.concurrent.Executors @@ -145,6 +146,7 @@ fun getThemeFromUri(uri: URI, withAlertOnException: Boolean = true): ThemeOverri runCatching { return yaml.decodeFromStream(it!!) }.onFailure { + Log.e(TAG, "Error while decoding theme: ${it.stackTraceToString()}") if (withAlertOnException) { AlertManager.shared.showAlertMsg( title = generalGetString(MR.strings.import_theme_error), @@ -280,6 +282,38 @@ fun saveFileFromUri(uri: URI, withAlertOnException: Boolean = true): CryptoFile? } } +fun saveWallpaperFile(uri: URI): String? { + val destFileName = generateNewFileName("wallpaper", "jpg", File(getWallpaperFilePath(""))) + val destFile = File(getWallpaperFilePath(destFileName)) + try { + val inputStream = uri.inputStream() + Files.copy(inputStream!!, destFile.toPath(), StandardCopyOption.REPLACE_EXISTING) + } catch (e: Exception) { + Log.e(TAG, "Error saving wallpaper file: ${e.stackTraceToString()}") + AlertManager.shared.showAlertMsg(generalGetString(MR.strings.error), e.stackTraceToString()) + return null + } + return destFile.name +} + +fun saveWallpaperFile(image: ImageBitmap): String { + val destFileName = generateNewFileName("wallpaper", "jpg", File(getWallpaperFilePath(""))) + val destFile = File(getWallpaperFilePath(destFileName)) + val dataResized = resizeImageToDataSize(image, false, maxDataSize = 5_000_000) + val output = FileOutputStream(destFile) + dataResized.use { + it.writeTo(output) + } + return destFile.name +} + +fun removeWallpaperFile(fileName: String? = null) { + File(getWallpaperFilePath("_")).parentFile.listFiles()?.forEach { + if (it.name == fileName) it.delete() + } + WallpaperType.cachedImages.remove(fileName) +} + fun createTmpFileAndDelete(onCreated: (File) -> T): T { val tmpFile = File(tmpDir, UUID.randomUUID().toString()) tmpFile.deleteOnExit() @@ -550,10 +584,33 @@ fun KeyChangeEffect( val initialKey = remember { key1 } val initialKey2 = remember { key2 } var anyChange by remember { mutableStateOf(false) } - LaunchedEffect(key1) { + LaunchedEffect(key1, key2) { if (anyChange || key1 != initialKey || key2 != initialKey2) { block() anyChange = true } } } + +/** + * Runs the [block] only after initial value of the [key1], or [key2], or [key3] changes, not after initial launch + * */ +@Composable +@NonRestartableComposable +fun KeyChangeEffect( + key1: Any?, + key2: Any?, + key3: Any?, + block: suspend CoroutineScope.() -> Unit +) { + val initialKey = remember { key1 } + val initialKey2 = remember { key2 } + val initialKey3 = remember { key3 } + var anyChange by remember { mutableStateOf(false) } + LaunchedEffect(key1, key2, key3) { + if (anyChange || key1 != initialKey || key2 != initialKey2 || key3 != initialKey3) { + block() + anyChange = true + } + } +} diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt index 5a37c860a0..65e1864935 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/localauth/LocalAuthView.kt @@ -34,7 +34,7 @@ fun LocalAuthView(m: ChatModel, authRequest: LocalAuthRequest) { } else { val r: LAResult = if (passcode.value == authRequest.password) { if (authRequest.selfDestruct && sdPassword != null && controller.ctrl == -1L) { - initChatControllerAndRunMigrations() + initChatControllerOnStart() } LAResult.Success } else { diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt index 9d204ad42c..1a94676f79 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/migration/MigrateToDevice.kt @@ -594,6 +594,7 @@ private fun MutableState.importArchive(archivePath: String, n chatInitControllerRemovingDatabases() } controller.apiDeleteStorage() + wallpapersDir.mkdirs() try { val config = ArchiveConfig(archivePath, parentTempDirectory = databaseExportDir.toString()) val archiveErrors = controller.apiImportArchive(config) @@ -669,7 +670,7 @@ private suspend fun MutableState.cleanUpOnBack(chatReceiver: 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() + initChatControllerOnStart() } else if (state is MigrationToState.DownloadProgress && state.ctrl != null) { stopArchiveDownloading(state.fileId, state.ctrl) } 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 9124808959..61ecee74e4 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 @@ -8,6 +8,7 @@ import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.text.AnnotatedString import dev.icerock.moko.resources.compose.stringResource import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.text.style.TextAlign @@ -35,9 +36,15 @@ fun SetNotificationsMode(m: ChatModel) { Column(Modifier.padding(horizontal = DEFAULT_PADDING * 1f)) { Text(stringResource(MR.strings.onboarding_notifications_mode_subtitle), Modifier.fillMaxWidth(), textAlign = TextAlign.Center) Spacer(Modifier.height(DEFAULT_PADDING * 2f)) - NotificationButton(currentMode, NotificationsMode.OFF, MR.strings.onboarding_notifications_mode_off, MR.strings.onboarding_notifications_mode_off_desc) - NotificationButton(currentMode, NotificationsMode.PERIODIC, MR.strings.onboarding_notifications_mode_periodic, MR.strings.onboarding_notifications_mode_periodic_desc) - NotificationButton(currentMode, NotificationsMode.SERVICE, MR.strings.onboarding_notifications_mode_service, MR.strings.onboarding_notifications_mode_service_desc) + SelectableCard(currentMode, NotificationsMode.OFF, stringResource(MR.strings.onboarding_notifications_mode_off), annotatedStringResource(MR.strings.onboarding_notifications_mode_off_desc)) { + currentMode.value = NotificationsMode.OFF + } + SelectableCard(currentMode, NotificationsMode.PERIODIC, stringResource(MR.strings.onboarding_notifications_mode_periodic), annotatedStringResource(MR.strings.onboarding_notifications_mode_periodic_desc)){ + currentMode.value = NotificationsMode.PERIODIC + } + SelectableCard(currentMode, NotificationsMode.SERVICE, stringResource(MR.strings.onboarding_notifications_mode_service), annotatedStringResource(MR.strings.onboarding_notifications_mode_service_desc)){ + currentMode.value = NotificationsMode.SERVICE + } } Spacer(Modifier.fillMaxHeight().weight(1f)) Box(Modifier.fillMaxWidth().padding(bottom = DEFAULT_PADDING_HALF), contentAlignment = Alignment.Center) { @@ -54,22 +61,22 @@ fun SetNotificationsMode(m: ChatModel) { expect fun SetNotificationsModeAdditions() @Composable -private fun NotificationButton(currentMode: MutableState, mode: NotificationsMode, title: StringResource, description: StringResource) { +fun SelectableCard(currentValue: State, newValue: T, title: String, description: AnnotatedString, onSelected: (T) -> Unit) { TextButton( - onClick = { currentMode.value = mode }, - border = BorderStroke(1.dp, color = if (currentMode.value == mode) MaterialTheme.colors.primary else MaterialTheme.colors.secondary.copy(alpha = 0.5f)), + onClick = { onSelected(newValue) }, + border = BorderStroke(1.dp, color = if (currentValue.value == newValue) MaterialTheme.colors.primary else MaterialTheme.colors.secondary.copy(alpha = 0.5f)), shape = RoundedCornerShape(35.dp), ) { - Column(Modifier.padding(horizontal = 10.dp).padding(top = 4.dp, bottom = 8.dp)) { + Column(Modifier.padding(horizontal = 10.dp).padding(top = 4.dp, bottom = 8.dp).fillMaxWidth()) { Text( - stringResource(title), + title, style = MaterialTheme.typography.h3, fontWeight = FontWeight.Medium, - color = if (currentMode.value == mode) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, + color = if (currentValue.value == newValue) MaterialTheme.colors.primary else MaterialTheme.colors.secondary, modifier = Modifier.padding(bottom = 8.dp).align(Alignment.CenterHorizontally), textAlign = TextAlign.Center ) - Text(annotatedStringResource(description), + Text(description, Modifier.align(Alignment.CenterHorizontally), fontSize = 15.sp, color = MaterialTheme.colors.onBackground, 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 df6c66deb4..caf021a381 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 @@ -271,7 +271,6 @@ private val versionDescriptions: List = listOf( icon = MR.images.ic_translate, titleId = MR.strings.v4_5_italian_interface, descrId = MR.strings.v4_5_italian_interface_descr, - link = "https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat" ) ) ), @@ -308,7 +307,6 @@ private val versionDescriptions: List = listOf( icon = MR.images.ic_translate, titleId = MR.strings.v4_6_chinese_spanish_interface, descrId = MR.strings.v4_6_chinese_spanish_interface_descr, - link = "https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat" ) ) ), @@ -330,7 +328,6 @@ private val versionDescriptions: List = listOf( icon = MR.images.ic_translate, titleId = MR.strings.v5_0_polish_interface, descrId = MR.strings.v5_0_polish_interface_descr, - link = "https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat" ) ) ), @@ -362,7 +359,6 @@ private val versionDescriptions: List = listOf( icon = MR.images.ic_translate, titleId = MR.strings.v5_1_japanese_portuguese_interface, descrId = MR.strings.whats_new_thanks_to_users_contribute_weblate, - link = "https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat" ) ) ), @@ -427,7 +423,6 @@ private val versionDescriptions: List = listOf( icon = MR.images.ic_translate, titleId = MR.strings.v5_3_new_interface_languages, descrId = MR.strings.v5_3_new_interface_languages_descr, - link = "https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat" ) ) ), @@ -491,7 +486,6 @@ private val versionDescriptions: List = listOf( icon = MR.images.ic_translate, titleId = MR.strings.v5_5_new_interface_languages, descrId = MR.strings.whats_new_thanks_to_users_contribute_weblate, - link = "https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat" ) ) ), @@ -554,7 +548,37 @@ private val versionDescriptions: List = listOf( icon = MR.images.ic_translate, titleId = MR.strings.v5_7_new_interface_languages, descrId = MR.strings.whats_new_thanks_to_users_contribute_weblate, - link = "https://github.com/simplex-chat/simplex-chat/tree/stable#help-translating-simplex-chat" + ) + ) + ), + VersionDescription( + version = "v5.8", + post = "https://simplex.chat/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.html", + features = listOf( + FeatureDescription( + icon = MR.images.ic_settings_ethernet, + titleId = MR.strings.v5_8_private_routing, + descrId = MR.strings.v5_8_private_routing_descr + ), + FeatureDescription( + icon = MR.images.ic_palette, + titleId = MR.strings.v5_8_chat_themes, + descrId = MR.strings.v5_8_chat_themes_descr + ), + FeatureDescription( + icon = MR.images.ic_security, + titleId = MR.strings.v5_8_safe_files, + descrId = MR.strings.v5_8_safe_files_descr + ), + FeatureDescription( + icon = MR.images.ic_battery_3_bar, + titleId = MR.strings.v5_8_message_delivery, + descrId = MR.strings.v5_8_message_delivery_descr + ), + FeatureDescription( + icon = MR.images.ic_translate, + titleId = MR.strings.v5_8_persian_ui, + descrId = MR.strings.whats_new_thanks_to_users_contribute_weblate ) ) ), 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 78e24e5e7e..84fe333ba8 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 @@ -1,37 +1,53 @@ package chat.simplex.common.views.usersettings import SectionBottomSpacer +import SectionDividerSpaced import SectionItemView import SectionItemViewSpaceBetween import SectionSpacer import SectionView -import androidx.compose.foundation.Image +import androidx.compose.foundation.* import androidx.compose.foundation.layout.* +import androidx.compose.foundation.lazy.grid.* +import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.* import androidx.compose.material.MaterialTheme.colors 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.* import androidx.compose.ui.graphics.* -import androidx.compose.ui.layout.ContentScale +import androidx.compose.ui.platform.LocalClipboardManager +import androidx.compose.ui.text.input.TextFieldValue +import androidx.compose.ui.unit.* import dev.icerock.moko.resources.compose.painterResource import dev.icerock.moko.resources.compose.stringResource -import androidx.compose.ui.unit.dp import chat.simplex.common.model.* +import chat.simplex.common.model.ChatController.appPrefs import chat.simplex.common.ui.theme.* import chat.simplex.common.views.helpers.* import chat.simplex.common.model.ChatModel +import chat.simplex.common.model.ChatModel.controller import chat.simplex.common.platform.* +import chat.simplex.common.ui.theme.ThemeManager.colorFromReadableHex +import chat.simplex.common.ui.theme.ThemeManager.toReadableHex +import chat.simplex.common.views.chat.item.PreviewChatItemView import chat.simplex.res.MR -import com.godaddy.android.colorpicker.* +import com.godaddy.android.colorpicker.ClassicColorPicker +import com.godaddy.android.colorpicker.HsvColor +import kotlinx.coroutines.Job +import kotlinx.coroutines.delay +import kotlinx.datetime.Clock import kotlinx.serialization.encodeToString +import java.io.File import java.net.URI import java.util.* import kotlin.collections.ArrayList +import kotlin.math.* @Composable -expect fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) +expect fun AppearanceView(m: ChatModel) object AppearanceScope { @Composable @@ -55,6 +71,7 @@ object AppearanceScope { onValueChange = { val diff = it % 2.5f appPreferences.profileImageCornerRadius.set(it + (if (diff >= 1.25f) -diff + 2.5f else -diff)) + saveThemeToDatabase(null) }, colors = SliderDefaults.colors( activeTickColor = Color.Transparent, @@ -66,90 +83,431 @@ object AppearanceScope { } @Composable - fun ThemesSection( - systemDarkTheme: SharedPreference, - showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - editColor: (ThemeColor, Color) -> Unit + fun ChatThemePreview( + theme: DefaultTheme, + wallpaperImage: ImageBitmap?, + wallpaperType: WallpaperType?, + backgroundColor: Color? = MaterialTheme.wallpaper.background, + tintColor: Color? = MaterialTheme.wallpaper.tint, + withMessages: Boolean = true ) { - val currentTheme by CurrentColors.collectAsState() - SectionView(stringResource(MR.strings.settings_section_title_themes)) { - val darkTheme = isSystemInDarkTheme() - val state = remember { derivedStateOf { currentTheme.name } } - ThemeSelector(state) { - ThemeManager.applyTheme(it, darkTheme) - } - if (state.value == DefaultTheme.SYSTEM.name) { - DarkThemeSelector(remember { systemDarkTheme.state }) { - ThemeManager.changeDarkTheme(it, darkTheme) + val themeBackgroundColor = MaterialTheme.colors.background + val backgroundColor = backgroundColor ?: wallpaperType?.defaultBackgroundColor(theme, MaterialTheme.colors.background) + val tintColor = tintColor ?: wallpaperType?.defaultTintColor(theme) + Column(Modifier + .drawWithCache { + if (wallpaperImage != null && wallpaperType != null && backgroundColor != null && tintColor != null) { + chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor) + } else { + onDrawBehind { + drawRect(themeBackgroundColor) + } } } + .padding(DEFAULT_PADDING_HALF) + ) { + if (withMessages) { + val alice = remember { ChatItem.getSampleData(1, CIDirection.DirectRcv(), Clock.System.now(), generalGetString(MR.strings.wallpaper_preview_hello_bob)) } + PreviewChatItemView(alice) + PreviewChatItemView( + ChatItem.getSampleData(2, CIDirection.DirectSnd(), Clock.System.now(), stringResource(MR.strings.wallpaper_preview_hello_alice), + quotedItem = CIQuote(alice.chatDir, alice.id, sentAt = alice.meta.itemTs, formattedText = alice.formattedText, content = MsgContent.MCText(alice.content.text)) + ) + ) + } else { + Box(Modifier.fillMaxSize()) + } } - SectionItemView(showSettingsModal { _ -> CustomizeThemeView(editColor) }) { Text(stringResource(MR.strings.customize_theme_title)) } } @Composable - fun CustomizeThemeView(editColor: (ThemeColor, Color) -> Unit) { + fun WallpaperPresetSelector( + selectedWallpaper: WallpaperType?, + baseTheme: DefaultTheme, + activeBackgroundColor: Color? = null, + activeTintColor: Color? = null, + currentColors: (WallpaperType?) -> ThemeManager.ActiveTheme, + onChooseType: (WallpaperType?) -> Unit, + ) { + val cornerRadius = 22 + + @Composable + fun Plus(tint: Color = MaterialTheme.colors.primary) { + Icon(painterResource(MR.images.ic_add), null, Modifier.size(25.dp), tint = tint) + } + + val backgrounds = PresetWallpaper.entries.toList() + + fun LazyGridScope.gridContent(width: Dp, height: Dp) { + @Composable + fun BackgroundItem(background: PresetWallpaper?) { + val checked = (background == null && (selectedWallpaper == null || selectedWallpaper == WallpaperType.Empty)) || selectedWallpaper?.samePreset(background) == true + Box( + Modifier + .size(width, height) + .clip(RoundedCornerShape(percent = cornerRadius)) + .border(1.dp, if (checked) MaterialTheme.colors.primary.copy(0.8f) else MaterialTheme.colors.onBackground.copy(if (isInDarkTheme()) 0.2f else 0.1f), RoundedCornerShape(percent = cornerRadius)) + .clickable { onChooseType(background?.toType(baseTheme)) }, + contentAlignment = Alignment.Center + ) { + if (background != null) { + val type = background.toType(baseTheme, if (checked) selectedWallpaper?.scale else null) + SimpleXThemeOverride(remember(background, selectedWallpaper, CurrentColors.collectAsState().value) { currentColors(type) }) { + ChatThemePreview( + baseTheme, + type.image, + type, + withMessages = false, + backgroundColor = if (checked) activeBackgroundColor ?: MaterialTheme.wallpaper.background else MaterialTheme.wallpaper.background, + tintColor = if (checked) activeTintColor ?: MaterialTheme.wallpaper.tint else MaterialTheme.wallpaper.tint + ) + } + } + } + } + + @Composable + fun OwnBackgroundItem(type: WallpaperType?) { + val overrides = remember(type, baseTheme, CurrentColors.collectAsState().value.wallpaper) { + currentColors(WallpaperType.Image("", null, null)) + } + val appWallpaper = overrides.wallpaper + val backgroundColor = appWallpaper.background + val tintColor = appWallpaper.tint + val wallpaperImage = appWallpaper.type.image + val checked = type is WallpaperType.Image && wallpaperImage != null + val remoteHostConnected = chatModel.remoteHostId != null + Box( + Modifier + .size(width, height) + .clip(RoundedCornerShape(percent = cornerRadius)) + .border(1.dp, if (type is WallpaperType.Image) MaterialTheme.colors.primary.copy(0.8f) else MaterialTheme.colors.onBackground.copy(0.1f), RoundedCornerShape(percent = cornerRadius)) + .clickable { onChooseType(WallpaperType.Image("", null, null)) }, + contentAlignment = Alignment.Center + ) { + + if (checked || wallpaperImage != null) { + ChatThemePreview( + baseTheme, + wallpaperImage, + if (checked) type else appWallpaper.type, + backgroundColor = if (checked) activeBackgroundColor ?: backgroundColor else backgroundColor, + tintColor = if (checked) activeTintColor ?: tintColor else tintColor, + withMessages = false + ) + } else if (remoteHostConnected) { + Plus(MaterialTheme.colors.error) + } else { + Plus() + } + } + } + + item { + BackgroundItem(null) + } + items(items = backgrounds) { background -> + BackgroundItem(background) + } + item { + OwnBackgroundItem(selectedWallpaper) + } + } + + SimpleXThemeOverride(remember(selectedWallpaper, CurrentColors.collectAsState().value) { currentColors(selectedWallpaper) }) { + ChatThemePreview( + baseTheme, + MaterialTheme.wallpaper.type.image, + selectedWallpaper, + backgroundColor = activeBackgroundColor ?: MaterialTheme.wallpaper.background, + tintColor = activeTintColor ?: MaterialTheme.wallpaper.tint, + ) + } + + if (appPlatform.isDesktop) { + val itemWidth = (DEFAULT_START_MODAL_WIDTH - DEFAULT_PADDING * 2 - DEFAULT_PADDING_HALF * 3) / 4 + val itemHeight = (DEFAULT_START_MODAL_WIDTH - DEFAULT_PADDING * 2) / 4 + val rows = ceil((PresetWallpaper.entries.size + 2) / 4f).roundToInt() + LazyVerticalGrid( + columns = GridCells.Fixed(4), + Modifier.height(itemHeight * rows + DEFAULT_PADDING_HALF * (rows - 1) + DEFAULT_PADDING * 2), + contentPadding = PaddingValues(DEFAULT_PADDING), + verticalArrangement = Arrangement.spacedBy(DEFAULT_PADDING_HALF), + horizontalArrangement = Arrangement.spacedBy(DEFAULT_PADDING_HALF), + ) { + gridContent(itemWidth, itemHeight) + } + } else { + LazyHorizontalGrid( + rows = GridCells.Fixed(1), + Modifier.height(80.dp + DEFAULT_PADDING * 2), + contentPadding = PaddingValues(DEFAULT_PADDING), + horizontalArrangement = Arrangement.spacedBy(DEFAULT_PADDING_HALF), + ) { + gridContent(80.dp, 80.dp) + } + } + } + + @Composable + fun ThemesSection(systemDarkTheme: SharedPreference) { + val currentTheme by CurrentColors.collectAsState() + val baseTheme = currentTheme.base + val wallpaperType = MaterialTheme.wallpaper.type + val themeUserDestination: MutableState?> = rememberSaveable(stateSaver = serializableSaver()) { + val currentUser = chatModel.currentUser.value + mutableStateOf( + if (currentUser?.uiThemes?.preferredMode(!currentTheme.colors.isLight) == null) null else currentUser.userId to currentUser.uiThemes + ) + } + val perUserTheme = remember(CurrentColors.collectAsState().value.base, chatModel.currentUser.value) { + mutableStateOf( + chatModel.currentUser.value?.uiThemes?.preferredMode(!CurrentColors.value.colors.isLight) ?: ThemeModeOverride() + ) + } + + fun updateThemeUserDestination() { + var (userId, themes) = themeUserDestination.value ?: return + themes = if (perUserTheme.value.mode == DefaultThemeMode.LIGHT) { + (themes ?: ThemeModeOverrides()).copy(light = perUserTheme.value) + } else { + (themes ?: ThemeModeOverrides()).copy(dark = perUserTheme.value) + } + themeUserDestination.value = userId to themes + } + + val onTypeCopyFromSameTheme = { type: WallpaperType? -> + if (themeUserDestination.value == null) { + ThemeManager.saveAndApplyWallpaper(baseTheme, type) + } else { + val wallpaperFiles = setOf(perUserTheme.value.wallpaper?.imageFile) + ThemeManager.copyFromSameThemeOverrides(type, null, perUserTheme) + val wallpaperFilesToDelete = wallpaperFiles - perUserTheme.value.wallpaper?.imageFile + wallpaperFilesToDelete.forEach(::removeWallpaperFile) + updateThemeUserDestination() + } + saveThemeToDatabase(themeUserDestination.value) + true + } + + val onTypeChange = { type: WallpaperType? -> + if (themeUserDestination.value == null) { + ThemeManager.saveAndApplyWallpaper(baseTheme, type) + } else { + ThemeManager.applyWallpaper(type, perUserTheme) + updateThemeUserDestination() + } + saveThemeToDatabase(themeUserDestination.value) + } + + val onImport = { to: URI -> + val filename = saveWallpaperFile(to) + if (filename != null) { + if (themeUserDestination.value == null) { + removeWallpaperFile((currentTheme.wallpaper.type as? WallpaperType.Image)?.filename) + } else { + removeWallpaperFile((perUserTheme.value.type as? WallpaperType.Image)?.filename) + } + onTypeChange(WallpaperType.Image(filename, 1f, WallpaperScaleType.FILL)) + } + } + + val currentColors = { type: WallpaperType? -> + // If applying for : + // - all themes: no overrides needed + // - specific user: only user overrides for currently selected theme are needed, because they will NOT be copied when other wallpaper is selected + val perUserOverride = if (themeUserDestination.value == null) null else if (wallpaperType.sameType(type)) chatModel.currentUser.value?.uiThemes else null + ThemeManager.currentColors(type, null, perUserOverride, appPrefs.themeOverrides.get()) + } + + val onChooseType: (WallpaperType?, FileChooserLauncher) -> Unit = { type: WallpaperType?, importWallpaperLauncher: FileChooserLauncher -> + when { + // don't have image in parent or already selected wallpaper with custom image + type is WallpaperType.Image && + ((wallpaperType is WallpaperType.Image && themeUserDestination.value?.second != null && chatModel.remoteHostId() == null) || + currentColors(type).wallpaper.type.image == null || + (currentColors(type).wallpaper.type.image != null && CurrentColors.value.wallpaper.type is WallpaperType.Image && themeUserDestination.value == null)) -> + withLongRunningApi { importWallpaperLauncher.launch("image/*") } + type is WallpaperType.Image && themeUserDestination.value == null -> onTypeChange(currentColors(type).wallpaper.type) + type is WallpaperType.Image && chatModel.remoteHostId() != null -> { /* do nothing when remote host connected */ } + type is WallpaperType.Image -> onTypeCopyFromSameTheme(currentColors(type).wallpaper.type) + (themeUserDestination.value != null && themeUserDestination.value?.second?.preferredMode(!CurrentColors.value.colors.isLight)?.type != type) || CurrentColors.value.wallpaper.type != type -> onTypeCopyFromSameTheme(type) + else -> onTypeChange(type) + } + } + + SectionView(stringResource(MR.strings.settings_section_title_themes)) { + Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + ThemeDestinationPicker(themeUserDestination) + Spacer(Modifier.height(DEFAULT_PADDING_HALF)) + + val importWallpaperLauncher = rememberFileChooserLauncher(true) { to: URI? -> + if (to != null) onImport(to) + } + + WallpaperPresetSelector( + selectedWallpaper = wallpaperType, + baseTheme = currentTheme.base, + currentColors = { type -> + currentColors(type) + }, + onChooseType = { onChooseType(it, importWallpaperLauncher) }, + ) + val type = MaterialTheme.wallpaper.type + if (type is WallpaperType.Image && (themeUserDestination.value == null || perUserTheme.value.wallpaper?.imageFile != null)) { + SectionItemView(disabled = chatModel.remoteHostId != null && themeUserDestination.value != null, click = { + if (themeUserDestination.value == null) { + val defaultActiveTheme = ThemeManager.defaultActiveTheme(appPrefs.themeOverrides.get()) + ThemeManager.saveAndApplyWallpaper(baseTheme, null) + ThemeManager.removeTheme(defaultActiveTheme?.themeId) + removeWallpaperFile(type.filename) + } else { + removeUserThemeModeOverrides(themeUserDestination, perUserTheme) + } + saveThemeToDatabase(themeUserDestination.value) + }) { + Text( + stringResource(MR.strings.theme_remove_image), + color = if (chatModel.remoteHostId != null && themeUserDestination.value != null) MaterialTheme.colors.secondary else MaterialTheme.colors.primary + ) + } + SectionSpacer() + } + + val state: State = remember(appPrefs.currentTheme.get()) { + derivedStateOf { + if (appPrefs.currentTheme.get() == DefaultTheme.SYSTEM_THEME_NAME) null else currentTheme.base.mode + } + } + ColorModeSelector(state) { + val newTheme = when (it) { + null -> DefaultTheme.SYSTEM_THEME_NAME + DefaultThemeMode.LIGHT -> DefaultTheme.LIGHT.themeName + DefaultThemeMode.DARK -> appPrefs.systemDarkTheme.get()!! + } + ThemeManager.applyTheme(newTheme) + saveThemeToDatabase(null) + } + + // Doesn't work on desktop when specified like remember { systemDarkTheme.state }, this is workaround + val darkModeState: State = remember(systemDarkTheme.get()) { derivedStateOf { systemDarkTheme.get() } } + DarkModeThemeSelector(darkModeState) { + ThemeManager.changeDarkTheme(it) + if (appPrefs.currentTheme.get() == DefaultTheme.SYSTEM_THEME_NAME) { + ThemeManager.applyTheme(appPrefs.currentTheme.get()!!) + } else if (appPrefs.currentTheme.get() != DefaultTheme.LIGHT.themeName) { + ThemeManager.applyTheme(appPrefs.systemDarkTheme.get()!!) + } + saveThemeToDatabase(null) + } + } + SectionItemView(click = { + val user = themeUserDestination.value + if (user == null) { + ModalManager.start.showModal { + val importWallpaperLauncher = rememberFileChooserLauncher(true) { to: URI? -> + if (to != null) onImport(to) + } + CustomizeThemeView { onChooseType(it, importWallpaperLauncher) } + } + } else { + ModalManager.start.showModalCloseable { close -> + UserWallpaperEditorModal(chatModel.remoteHostId(), user.first, close) + } + } + }) { + Text(stringResource(MR.strings.customize_theme_title)) + } + } + + @Composable + fun CustomizeThemeView(onChooseType: (WallpaperType?) -> Unit) { ColumnWithScrollBar( Modifier.fillMaxWidth(), ) { val currentTheme by CurrentColors.collectAsState() AppBarTitle(stringResource(MR.strings.customize_theme_title)) + val wallpaperImage = MaterialTheme.wallpaper.type.image + val wallpaperType = MaterialTheme.wallpaper.type + val baseTheme = CurrentColors.collectAsState().value.base - SectionView(stringResource(MR.strings.theme_colors_section_title)) { - SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY, currentTheme.colors.primary) }) { - val title = generalGetString(MR.strings.color_primary) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.primary) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY_VARIANT, currentTheme.colors.primaryVariant) }) { - val title = generalGetString(MR.strings.color_primary_variant) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.primaryVariant) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY, currentTheme.colors.secondary) }) { - val title = generalGetString(MR.strings.color_secondary) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.secondary) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY_VARIANT, currentTheme.colors.secondaryVariant) }) { - val title = generalGetString(MR.strings.color_secondary_variant) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.secondaryVariant) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.BACKGROUND, currentTheme.colors.background) }) { - val title = generalGetString(MR.strings.color_background) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.background) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.SURFACE, currentTheme.colors.surface) }) { - val title = generalGetString(MR.strings.color_surface) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = colors.surface) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.TITLE, currentTheme.appColors.title) }) { - val title = generalGetString(MR.strings.color_title) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.title) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.SENT_MESSAGE, currentTheme.appColors.sentMessage) }) { - val title = generalGetString(MR.strings.color_sent_message) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.sentMessage) - } - SectionItemViewSpaceBetween({ editColor(ThemeColor.RECEIVED_MESSAGE, currentTheme.appColors.receivedMessage) }) { - val title = generalGetString(MR.strings.color_received_message) - Text(title) - Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.receivedMessage) - } + val editColor = { name: ThemeColor -> + editColor( + name, + wallpaperType, + wallpaperImage, + onColorChange = { color -> + ThemeManager.saveAndApplyThemeColor(baseTheme, name, color) + saveThemeToDatabase(null) + } + ) } - val isInDarkTheme = isInDarkTheme() - if (currentTheme.base.hasChangedAnyColor(currentTheme.colors, currentTheme.appColors)) { - SectionItemView({ ThemeManager.resetAllThemeColors(darkForSystemTheme = isInDarkTheme) }) { + + WallpaperPresetSelector( + selectedWallpaper = wallpaperType, + baseTheme = currentTheme.base, + currentColors = { type -> + ThemeManager.currentColors(type, null, null, appPrefs.themeOverrides.get()) + }, + onChooseType = onChooseType + ) + + val type = MaterialTheme.wallpaper.type + if (type is WallpaperType.Image) { + SectionItemView(disabled = chatModel.remoteHostId != null, click = { + val defaultActiveTheme = ThemeManager.defaultActiveTheme(appPrefs.themeOverrides.get()) + ThemeManager.saveAndApplyWallpaper(baseTheme, null) + ThemeManager.removeTheme(defaultActiveTheme?.themeId) + removeWallpaperFile(type.filename) + saveThemeToDatabase(null) + }) { + Text( + stringResource(MR.strings.theme_remove_image), + color = if (chatModel.remoteHostId == null) MaterialTheme.colors.primary else MaterialTheme.colors.secondary + ) + } + SectionSpacer() + } + + SectionView(stringResource(MR.strings.settings_section_title_chat_colors).uppercase()) { + WallpaperSetupView( + wallpaperType, + baseTheme, + MaterialTheme.wallpaper, + MaterialTheme.appColors.sentMessage, + MaterialTheme.appColors.sentQuote, + MaterialTheme.appColors.receivedMessage, + MaterialTheme.appColors.receivedQuote, + editColor = { name -> + editColor(name) + }, + onTypeChange = { type -> + ThemeManager.saveAndApplyWallpaper(baseTheme, type) + saveThemeToDatabase(null) + }, + ) + } + SectionDividerSpaced(maxTopPadding = true) + + CustomizeThemeColorsSection(currentTheme) { name -> + editColor(name) + } + + SectionSpacer() + + val currentOverrides = remember(currentTheme) { ThemeManager.defaultActiveTheme(appPrefs.themeOverrides.get()) } + val canResetColors = currentTheme.base.hasChangedAnyColor(currentOverrides) + if (canResetColors) { + SectionItemView({ + ThemeManager.resetAllThemeColors() + saveThemeToDatabase(null) + }) { Text(generalGetString(MR.strings.reset_color), color = colors.primary) } + SectionSpacer() } - SectionSpacer() + SectionView { val theme = remember { mutableStateOf(null as String?) } val exportThemeLauncher = rememberFileChooserLauncher(false) { to: URI? -> @@ -161,9 +519,11 @@ object AppearanceScope { } } SectionItemView({ - val overrides = ThemeManager.currentThemeOverridesForExport(isInDarkTheme) - theme.value = yaml.encodeToString(overrides) - withLongRunningApi { exportThemeLauncher.launch("simplex.theme")} + val overrides = ThemeManager.currentThemeOverridesForExport(null, null/*chatModel.currentUser.value?.uiThemes*/) + val lines = yaml.encodeToString(overrides).lines() + // Removing theme id without using custom serializer or data class + theme.value = lines.subList(1, lines.size).joinToString("\n") + withLongRunningApi { exportThemeLauncher.launch("simplex.theme") } }) { Text(generalGetString(MR.strings.export_theme), color = colors.primary) } @@ -171,7 +531,8 @@ object AppearanceScope { if (to != null) { val theme = getThemeFromUri(to) if (theme != null) { - ThemeManager.saveAndApplyThemeOverrides(theme, isInDarkTheme) + ThemeManager.saveAndApplyThemeOverrides(theme) + saveThemeToDatabase(null) } } } @@ -184,49 +545,339 @@ object AppearanceScope { } } - @Composable - fun ColorEditor( - name: ThemeColor, - initialColor: Color, - close: () -> Unit, - ) { - Column( - Modifier - .fillMaxWidth() - ) { - AppBarTitle(name.text) - var currentColor by remember { mutableStateOf(initialColor) } - ColorPicker(initialColor) { - currentColor = it + private var updateBackendJob: Job = Job() + private fun saveThemeToDatabase(themeUserDestination: Pair?) { + val remoteHostId = chatModel.remoteHostId() + val oldThemes = chatModel.currentUser.value?.uiThemes + if (themeUserDestination != null) { + // Update before save to make it work seamless + chatModel.updateCurrentUserUiThemes(remoteHostId, themeUserDestination.second) + } + updateBackendJob.cancel() + updateBackendJob = withBGApi { + delay(300) + if (themeUserDestination == null) { + controller.apiSaveAppSettings(AppSettings.current.prepareForExport()) + } else if (!controller.apiSetUserUIThemes(remoteHostId, themeUserDestination.first, themeUserDestination.second)) { + // If failed to apply for some reason return the old themes + chatModel.updateCurrentUserUiThemes(remoteHostId, oldThemes) } + } + } - SectionSpacer() - val isInDarkTheme = isInDarkTheme() - TextButton( - onClick = { - ThemeManager.saveAndApplyThemeColor(name, currentColor, isInDarkTheme) - close() - }, - Modifier.align(Alignment.CenterHorizontally), - colors = ButtonDefaults.textButtonColors(contentColor = currentColor) - ) { - Text(generalGetString(MR.strings.save_color)) + fun editColor(name: ThemeColor, wallpaperType: WallpaperType, wallpaperImage: ImageBitmap?, onColorChange: (Color?) -> Unit) { + ModalManager.start.showModal { + val baseTheme = CurrentColors.collectAsState().value.base + val wallpaperBackgroundColor = MaterialTheme.wallpaper.background ?: wallpaperType.defaultBackgroundColor(baseTheme, MaterialTheme.colors.background) + val wallpaperTintColor = MaterialTheme.wallpaper.tint ?: wallpaperType.defaultTintColor(baseTheme) + val initialColor: Color = when (name) { + ThemeColor.WALLPAPER_BACKGROUND -> wallpaperBackgroundColor + ThemeColor.WALLPAPER_TINT -> wallpaperTintColor + ThemeColor.PRIMARY -> MaterialTheme.colors.primary + ThemeColor.PRIMARY_VARIANT -> MaterialTheme.colors.primaryVariant + ThemeColor.SECONDARY -> MaterialTheme.colors.secondary + ThemeColor.SECONDARY_VARIANT -> MaterialTheme.colors.secondaryVariant + ThemeColor.BACKGROUND -> MaterialTheme.colors.background + ThemeColor.SURFACE -> MaterialTheme.colors.surface + ThemeColor.TITLE -> MaterialTheme.appColors.title + ThemeColor.PRIMARY_VARIANT2 -> MaterialTheme.appColors.primaryVariant2 + ThemeColor.SENT_MESSAGE -> MaterialTheme.appColors.sentMessage + ThemeColor.SENT_QUOTE -> MaterialTheme.appColors.sentQuote + ThemeColor.RECEIVED_MESSAGE -> MaterialTheme.appColors.receivedMessage + ThemeColor.RECEIVED_QUOTE -> MaterialTheme.appColors.receivedQuote + } + ColorEditor(name, initialColor, baseTheme, MaterialTheme.wallpaper.type, wallpaperImage, currentColors = { CurrentColors.value }, + onColorChange = onColorChange + ) + } + } + + @Composable + fun ModalData.UserWallpaperEditorModal(remoteHostId: Long?, userId: Long, close: () -> Unit) { + val themes = remember(chatModel.currentUser.value) { mutableStateOf(chatModel.currentUser.value?.uiThemes ?: ThemeModeOverrides()) } + val globalThemeUsed = remember { stateGetOrPut("globalThemeUsed") { false } } + val initialTheme = remember(CurrentColors.collectAsState().value.base) { + val preferred = themes.value.preferredMode(!CurrentColors.value.colors.isLight) + globalThemeUsed.value = preferred == null + preferred ?: ThemeManager.defaultActiveTheme(chatModel.currentUser.value?.uiThemes, appPrefs.themeOverrides.get()) + } + UserWallpaperEditor( + initialTheme, + applyToMode = if (themes.value.light == themes.value.dark) null else initialTheme.mode, + globalThemeUsed = globalThemeUsed, + save = { applyToMode, newTheme -> + save(applyToMode, newTheme, themes.value, userId, remoteHostId) + }) + KeyChangeEffect(chatModel.currentUser.value?.userId, chatModel.remoteHostId) { + close() + } + } + + suspend fun save( + applyToMode: DefaultThemeMode?, + newTheme: ThemeModeOverride?, + themes: ThemeModeOverrides?, + userId: Long, + remoteHostId: Long? + ) { + val unchangedThemes: ThemeModeOverrides = themes ?: ThemeModeOverrides() + val wallpaperFiles = setOf(unchangedThemes.light?.wallpaper?.imageFile, unchangedThemes.dark?.wallpaper?.imageFile) + var changedThemes: ThemeModeOverrides? = unchangedThemes + val changed = newTheme?.copy(wallpaper = newTheme.wallpaper?.withFilledWallpaperPath()) + changedThemes = when (applyToMode) { + null -> changedThemes?.copy(light = changed?.copy(mode = DefaultThemeMode.LIGHT), dark = changed?.copy(mode = DefaultThemeMode.DARK)) + DefaultThemeMode.LIGHT -> changedThemes?.copy(light = changed?.copy(mode = applyToMode)) + DefaultThemeMode.DARK -> changedThemes?.copy(dark = changed?.copy(mode = applyToMode)) + } + changedThemes = if (changedThemes?.light != null || changedThemes?.dark != null) { + val light = changedThemes.light + val dark = changedThemes.dark + val currentMode = CurrentColors.value.base.mode + // same image file for both modes, copy image to make them as different files + if (light?.wallpaper?.imageFile != null && dark?.wallpaper?.imageFile != null && light.wallpaper.imageFile == dark.wallpaper.imageFile) { + val imageFile = if (currentMode == DefaultThemeMode.LIGHT) { + dark.wallpaper.imageFile + } else { + light.wallpaper.imageFile + } + val filePath = saveWallpaperFile(File(getWallpaperFilePath(imageFile)).toURI()) + changedThemes = if (currentMode == DefaultThemeMode.LIGHT) { + changedThemes.copy(dark = dark.copy(wallpaper = dark.wallpaper.copy(imageFile = filePath))) + } else { + changedThemes.copy(light = light.copy(wallpaper = light.wallpaper.copy(imageFile = filePath))) + } + } + changedThemes + } else { + null + } + + val wallpaperFilesToDelete = wallpaperFiles - changedThemes?.light?.wallpaper?.imageFile - changedThemes?.dark?.wallpaper?.imageFile + wallpaperFilesToDelete.forEach(::removeWallpaperFile) + + val oldThemes = chatModel.currentUser.value?.uiThemes + // Update before save to make it work seamless + chatModel.updateCurrentUserUiThemes(remoteHostId, changedThemes) + updateBackendJob.cancel() + updateBackendJob = withBGApi { + delay(300) + if (!controller.apiSetUserUIThemes(remoteHostId, userId, changedThemes)) { + // If failed to apply for some reason return the old themes + chatModel.updateCurrentUserUiThemes(remoteHostId, oldThemes) } } } @Composable - fun ColorPicker(initialColor: Color, onColorChanged: (Color) -> Unit) { - ClassicColorPicker(modifier = Modifier - .fillMaxWidth() - .height(300.dp), - color = HsvColor.from(color = initialColor), showAlphaBar = true, - onColorChanged = { color: HsvColor -> - onColorChanged(color.toColor()) + fun ThemeDestinationPicker(themeUserDestination: MutableState?>) { + val themeUserDest = remember(themeUserDestination.value?.first) { mutableStateOf(themeUserDestination.value?.first) } + LaunchedEffect(themeUserDestination.value) { + if (themeUserDestination.value == null) { + // Easiest way to hide per-user customization. + // Otherwise, it would be needed to make global variable and to use it everywhere for making a decision to include these overrides into active theme constructing or not + chatModel.currentUser.value = chatModel.currentUser.value?.copy(uiThemes = null) + } else { + chatModel.updateCurrentUserUiThemes(chatModel.remoteHostId(), chatModel.users.firstOrNull { it.user.userId == chatModel.currentUser.value?.userId }?.user?.uiThemes) } - ) + } + DisposableEffect(Unit) { + onDispose { + // Skip when Appearance screen is not hidden yet + if (ModalManager.start.hasModalsOpen()) return@onDispose + // Restore user overrides from stored list of users + chatModel.updateCurrentUserUiThemes(chatModel.remoteHostId(), chatModel.users.firstOrNull { it.user.userId == chatModel.currentUser.value?.userId }?.user?.uiThemes) + themeUserDestination.value = if (chatModel.currentUser.value?.uiThemes == null) null else chatModel.currentUser.value?.userId!! to chatModel.currentUser.value?.uiThemes + } + } + + val values by remember(chatModel.users.toList()) { mutableStateOf( + listOf(null as Long? to generalGetString(MR.strings.theme_destination_app_theme)) + + + chatModel.users.filter { it.user.activeUser }.map { + it.user.userId to it.user.chatViewName + }, + ) + } + if (values.any { it.first == themeUserDestination.value?.first }) { + ExposedDropDownSettingRow( + generalGetString(MR.strings.chat_theme_apply_to_mode), + values, + themeUserDest, + icon = null, + enabled = remember { mutableStateOf(true) }, + onSelected = { userId -> + themeUserDest.value = userId + if (userId != null) { + themeUserDestination.value = userId to chatModel.users.firstOrNull { it.user.userId == userId }?.user?.uiThemes + } else { + themeUserDestination.value = null + } + if (userId != null && userId != chatModel.currentUser.value?.userId) { + withBGApi { + controller.showProgressIfNeeded { + chatModel.controller.changeActiveUser(chatModel.remoteHostId(), userId, null) + } + } + } + } + ) + } else { + themeUserDestination.value = null + } } + @Composable + fun CustomizeThemeColorsSection(currentTheme: ThemeManager.ActiveTheme, editColor: (ThemeColor) -> Unit) { + SectionView(stringResource(MR.strings.theme_colors_section_title)) { + SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY) }) { + val title = generalGetString(MR.strings.color_primary) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.colors.primary) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY_VARIANT) }) { + val title = generalGetString(MR.strings.color_primary_variant) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.colors.primaryVariant) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.PRIMARY_VARIANT2) }) { + val title = generalGetString(MR.strings.color_primary_variant2) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.primaryVariant2) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY) }) { + val title = generalGetString(MR.strings.color_secondary) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.colors.secondary) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.SECONDARY_VARIANT) }) { + val title = generalGetString(MR.strings.color_secondary_variant) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.colors.secondaryVariant) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.BACKGROUND) }) { + val title = generalGetString(MR.strings.color_background) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.colors.background) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.SURFACE) }) { + val title = generalGetString(MR.strings.color_surface) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.colors.surface) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.TITLE) }) { + val title = generalGetString(MR.strings.color_title) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = currentTheme.appColors.title) + } + } + } + + @Composable + fun ColorEditor( + name: ThemeColor, + initialColor: Color, + theme: DefaultTheme, + wallpaperType: WallpaperType?, + wallpaperImage: ImageBitmap?, + previewBackgroundColor: Color? = MaterialTheme.wallpaper.background, + previewTintColor: Color? = MaterialTheme.wallpaper.tint, + currentColors: () -> ThemeManager.ActiveTheme, + onColorChange: (Color?) -> Unit, + ) { + ColumnWithScrollBar( + Modifier + .fillMaxWidth() + ) { + AppBarTitle(name.text) + + val supportedLiveChange = name in listOf(ThemeColor.SECONDARY, ThemeColor.BACKGROUND, ThemeColor.SURFACE, ThemeColor.RECEIVED_MESSAGE, ThemeColor.SENT_MESSAGE, ThemeColor.SENT_QUOTE, ThemeColor.WALLPAPER_BACKGROUND, ThemeColor.WALLPAPER_TINT) + if (supportedLiveChange) { + SimpleXThemeOverride(currentColors()) { + ChatThemePreview(theme, wallpaperImage, wallpaperType, previewBackgroundColor, previewTintColor) + } + SectionSpacer() + } + + var currentColor by remember { mutableStateOf(initialColor) } + val togglePicker = remember { mutableStateOf(false) } + Box(Modifier.padding(horizontal = DEFAULT_PADDING)) { + if (togglePicker.value) { + ColorPicker(currentColor, showAlphaBar = wallpaperType is WallpaperType.Image || currentColor.alpha < 1f) { + currentColor = it + onColorChange(currentColor) + } + } else { + ColorPicker(currentColor, showAlphaBar = wallpaperType is WallpaperType.Image || currentColor.alpha < 1f) { + currentColor = it + onColorChange(currentColor) + } + } + } + var allowReloadPicker by remember { mutableStateOf(false) } + KeyChangeEffect(wallpaperType) { + allowReloadPicker = true + } + KeyChangeEffect(initialColor) { + if (initialColor != currentColor && allowReloadPicker) { + currentColor = initialColor + togglePicker.value = !togglePicker.value + } + allowReloadPicker = false + } + val clipboard = LocalClipboardManager.current + val hexTrimmed = currentColor.toReadableHex().replaceFirst("#ff", "#") + val savedColor by remember(wallpaperType) { mutableStateOf(initialColor) } + + Row(Modifier.padding(horizontal = DEFAULT_PADDING, vertical = DEFAULT_PADDING_HALF).height(46.dp)) { + Box(Modifier.weight(1f).fillMaxHeight().background(savedColor).clickable { + currentColor = savedColor + onColorChange(currentColor) + togglePicker.value = !togglePicker.value + }) + Box(Modifier.weight(1f).fillMaxHeight().background(currentColor).clickable { + clipboard.shareText(hexTrimmed) + }) + } + if (appPrefs.developerTools.get()) { + Row(Modifier.fillMaxWidth().padding(start = DEFAULT_PADDING_HALF, end = DEFAULT_PADDING), horizontalArrangement = Arrangement.SpaceEvenly, verticalAlignment = Alignment.CenterVertically) { + val textFieldState = remember { mutableStateOf(TextFieldValue(hexTrimmed)) } + KeyChangeEffect(hexTrimmed) { + textFieldState.value = textFieldState.value.copy(hexTrimmed) + } + DefaultBasicTextField( + Modifier.fillMaxWidth(), + textFieldState, + leadingIcon = { + IconButton(onClick = { clipboard.shareText(hexTrimmed) }) { + Icon(painterResource(MR.images.ic_content_copy), generalGetString(MR.strings.copy_verb), Modifier.size(26.dp), tint = MaterialTheme.colors.primary) + } + }, + onValueChange = { value -> + val color = value.text.trim('#', ' ') + if (color.length == 6 || color.length == 8) { + currentColor = if (color.length == 6) ("ff$color").colorFromReadableHex() else color.colorFromReadableHex() + onColorChange(currentColor) + textFieldState.value = value.copy(currentColor.toReadableHex().replaceFirst("#ff", "#")) + togglePicker.value = !togglePicker.value + } else { + textFieldState.value = value + } + } + ) + } + } + SectionItemView({ + allowReloadPicker = true + onColorChange(null) + }) { + Text(generalGetString(MR.strings.reset_single_color), color = colors.primary) + } + SectionSpacer() + } + } + + + @Composable fun LangSelector(state: State, onSelected: (String) -> Unit) { // Should be the same as in app/build.gradle's `android.defaultConfig.resConfigs` @@ -238,6 +889,7 @@ object AppearanceScope { "cs" to "Čeština", "de" to "Deutsch", "es" to "Español", + "fa" to "فارسی", "fi" to "Suomi", "fr" to "Français", "hu" to "Magyar", @@ -254,7 +906,7 @@ object AppearanceScope { "uk" to "Українська", "zh-CN" to "简体中文" ) - val values by remember(ChatController.appPrefs.appLanguage.state.value) { mutableStateOf(supportedLanguages.map { it.key to it.value }) } + val values by remember(appPrefs.appLanguage.state.value) { mutableStateOf(supportedLanguages.map { it.key to it.value }) } ExposedDropDownSettingRow( generalGetString(MR.strings.settings_section_title_language).lowercase().replaceFirstChar { if (it.isLowerCase()) it.titlecase(Locale.US) else it.toString() }, values, @@ -266,13 +918,18 @@ object AppearanceScope { } @Composable - private fun ThemeSelector(state: State, onSelected: (String) -> Unit) { - val darkTheme = chat.simplex.common.ui.theme.isSystemInDarkTheme() - val values by remember(ChatController.appPrefs.appLanguage.state.value) { - mutableStateOf(ThemeManager.allThemes(darkTheme).map { it.second.name to it.third }) + private fun ColorModeSelector(state: State, onSelected: (DefaultThemeMode?) -> Unit) { + val values by remember(appPrefs.appLanguage.state.value) { + mutableStateOf( + listOf( + null to generalGetString(MR.strings.color_mode_system), + DefaultThemeMode.LIGHT to generalGetString(MR.strings.color_mode_light), + DefaultThemeMode.DARK to generalGetString(MR.strings.color_mode_dark) + ) + ) } ExposedDropDownSettingRow( - generalGetString(MR.strings.theme), + generalGetString(MR.strings.color_mode), values, state, icon = null, @@ -282,15 +939,16 @@ object AppearanceScope { } @Composable - private fun DarkThemeSelector(state: State, onSelected: (String) -> Unit) { + private fun DarkModeThemeSelector(state: State, onSelected: (String) -> Unit) { val values by remember { val darkThemes = ArrayList>() - darkThemes.add(DefaultTheme.DARK.name to generalGetString(MR.strings.theme_dark)) - darkThemes.add(DefaultTheme.SIMPLEX.name to generalGetString(MR.strings.theme_simplex)) + darkThemes.add(DefaultTheme.DARK.themeName to generalGetString(MR.strings.theme_dark)) + darkThemes.add(DefaultTheme.SIMPLEX.themeName to generalGetString(MR.strings.theme_simplex)) + darkThemes.add(DefaultTheme.BLACK.themeName to generalGetString(MR.strings.theme_black)) mutableStateOf(darkThemes.toList()) } ExposedDropDownSettingRow( - generalGetString(MR.strings.dark_theme), + generalGetString(MR.strings.dark_mode_colors), values, state, icon = null, @@ -303,3 +961,109 @@ object AppearanceScope { //} } +@Composable +fun WallpaperSetupView( + wallpaperType: WallpaperType?, + theme: DefaultTheme, + initialWallpaper: AppWallpaper?, + initialSentColor: Color, + initialSentQuoteColor: Color, + initialReceivedColor: Color, + initialReceivedQuoteColor: Color, + editColor: (ThemeColor) -> Unit, + onTypeChange: (WallpaperType?) -> Unit, +) { + if (wallpaperType is WallpaperType.Image) { + val state = remember(wallpaperType.scaleType, initialWallpaper?.type) { mutableStateOf(wallpaperType.scaleType ?: (initialWallpaper?.type as? WallpaperType.Image)?.scaleType ?: WallpaperScaleType.FILL) } + val values = remember { + WallpaperScaleType.entries.map { it to generalGetString(it.text) } + } + ExposedDropDownSettingRow( + stringResource(MR.strings.wallpaper_scale), + values, + state, + onSelected = { scaleType -> + onTypeChange(wallpaperType.copy(scaleType = scaleType)) + } + ) + } + + if (wallpaperType is WallpaperType.Preset || (wallpaperType is WallpaperType.Image && wallpaperType.scaleType == WallpaperScaleType.REPEAT)) { + val state = remember(wallpaperType, initialWallpaper?.type?.scale) { mutableStateOf(wallpaperType.scale ?: initialWallpaper?.type?.scale ?: 1f) } + Row(Modifier.padding(horizontal = DEFAULT_PADDING), verticalAlignment = Alignment.CenterVertically) { + Text("${state.value}".substring(0, min("${state.value}".length, 4)), Modifier.width(50.dp)) + Slider( + state.value, + valueRange = 0.5f..2f, + onValueChange = { + if (wallpaperType is WallpaperType.Preset) { + onTypeChange(wallpaperType.copy(scale = it)) + } else if (wallpaperType is WallpaperType.Image) { + onTypeChange(wallpaperType.copy(scale = it)) + } + } + ) + } + } + + if (wallpaperType is WallpaperType.Preset || wallpaperType is WallpaperType.Image) { + val wallpaperBackgroundColor = initialWallpaper?.background ?: wallpaperType.defaultBackgroundColor(theme, MaterialTheme.colors.background) + SectionItemViewSpaceBetween({ editColor(ThemeColor.WALLPAPER_BACKGROUND) }) { + val title = generalGetString(MR.strings.color_wallpaper_background) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = wallpaperBackgroundColor) + } + val wallpaperTintColor = initialWallpaper?.tint ?: wallpaperType.defaultTintColor(theme) + SectionItemViewSpaceBetween({ editColor(ThemeColor.WALLPAPER_TINT) }) { + val title = generalGetString(MR.strings.color_wallpaper_tint) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = wallpaperTintColor) + } + SectionSpacer() + } + + SectionItemViewSpaceBetween({ editColor(ThemeColor.SENT_MESSAGE) }) { + val title = generalGetString(MR.strings.color_sent_message) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = initialSentColor) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.SENT_QUOTE) }) { + val title = generalGetString(MR.strings.color_sent_quote) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = initialSentQuoteColor) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.RECEIVED_MESSAGE) }) { + val title = generalGetString(MR.strings.color_received_message) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = initialReceivedColor) + } + SectionItemViewSpaceBetween({ editColor(ThemeColor.RECEIVED_QUOTE) }) { + val title = generalGetString(MR.strings.color_received_quote) + Text(title) + Icon(painterResource(MR.images.ic_circle_filled), title, tint = initialReceivedQuoteColor) + } +} + +@Composable +private fun ColorPicker(initialColor: Color, showAlphaBar: Boolean, onColorChanged: (Color) -> Unit) { + ClassicColorPicker(modifier = Modifier + .fillMaxWidth() + .height(300.dp), + color = HsvColor.from(color = initialColor), + showAlphaBar = showAlphaBar, + onColorChanged = { color: HsvColor -> + onColorChanged(color.toColor()) + } + ) +} + +private fun removeUserThemeModeOverrides(themeUserDestination: MutableState?>, perUserTheme: MutableState) { + val dest = themeUserDestination.value ?: return + perUserTheme.value = ThemeModeOverride() + themeUserDestination.value = dest.first to null + val wallpaperFilesToDelete = listOf( + (chatModel.currentUser.value?.uiThemes?.light?.type as? WallpaperType.Image)?.filename, + (chatModel.currentUser.value?.uiThemes?.dark?.type as? WallpaperType.Image)?.filename + ) + wallpaperFilesToDelete.forEach(::removeWallpaperFile) +} 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 b2124df988..5898ccb657 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 @@ -7,6 +7,7 @@ import SectionItemView import SectionItemWithValue import SectionView import SectionViewSelectable +import SectionViewSelectableCards import TextIconSpaced import androidx.compose.foundation.* import androidx.compose.foundation.layout.* @@ -250,17 +251,17 @@ fun NetworkAndServersView() { Divider(Modifier.padding(start = DEFAULT_PADDING_HALF, top = 24.dp, end = DEFAULT_PADDING_HALF, bottom = 30.dp)) } -// if (currentRemoteHost == null) { -// SectionView(generalGetString(MR.strings.settings_section_title_private_message_routing)) { -// SMPProxyModePicker(smpProxyMode, showModal, updateSMPProxyMode) -// SMPProxyFallbackPicker(smpProxyFallback, showModal, updateSMPProxyFallback, enabled = remember { mutableStateOf(smpProxyMode.value != SMPProxyMode.Never) }) -// SettingsPreferenceItem(painterResource(MR.images.ic_arrow_forward), stringResource(MR.strings.private_routing_show_message_status), chatModel.controller.appPrefs.showSentViaProxy) -// } -// SectionCustomFooter { -// Text(stringResource(MR.strings.private_routing_explanation)) -// } -// Divider(Modifier.padding(start = DEFAULT_PADDING_HALF, top = 32.dp, end = DEFAULT_PADDING_HALF, bottom = 30.dp)) -// } + if (currentRemoteHost == null) { + SectionView(generalGetString(MR.strings.settings_section_title_private_message_routing)) { + SMPProxyModePicker(smpProxyMode, showModal, updateSMPProxyMode) + SMPProxyFallbackPicker(smpProxyFallback, showModal, updateSMPProxyFallback, enabled = remember { mutableStateOf(smpProxyMode.value != SMPProxyMode.Never) }) + SettingsPreferenceItem(painterResource(MR.images.ic_arrow_forward), stringResource(MR.strings.private_routing_show_message_status), chatModel.controller.appPrefs.showSentViaProxy) + } + SectionCustomFooter { + Text(stringResource(MR.strings.private_routing_explanation)) + } + Divider(Modifier.padding(start = DEFAULT_PADDING_HALF, top = 32.dp, end = DEFAULT_PADDING_HALF, bottom = 30.dp)) + } SectionView(generalGetString(MR.strings.settings_section_title_calls)) { SettingsActionItem(painterResource(MR.images.ic_electrical_services), stringResource(MR.strings.webrtc_ice_servers), { ModalManager.start.showModal { RTCServersView(m) } }) @@ -555,7 +556,7 @@ private fun SMPProxyModePicker( Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.network_smp_proxy_mode_private_routing)) - SectionViewSelectable(null, smpProxyMode, values, updateSMPProxyMode) + SectionViewSelectableCards(null, smpProxyMode, values, updateSMPProxyMode) } } } @@ -592,7 +593,7 @@ private fun SMPProxyFallbackPicker( Modifier.fillMaxWidth(), ) { AppBarTitle(stringResource(MR.strings.network_smp_proxy_fallback_allow_downgrade)) - SectionViewSelectable(null, smpProxyFallback, values, updateSMPProxyFallback) + SectionViewSelectableCards(null, smpProxyFallback, values, updateSMPProxyFallback) } } } 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 298eb39737..0b5033aca3 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 @@ -144,7 +144,7 @@ fun SettingsLayout( 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) + SettingsActionItem(painterResource(MR.images.ic_light_mode), stringResource(MR.strings.appearance_settings), showSettingsModal { AppearanceView(it) }, extraPadding = true) DatabaseItem(encrypted, passphraseSaved, showSettingsModal { DatabaseView(it, showSettingsModal) }, stopped) } SectionDividerSpaced() 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 52dd8e6479..ef4edcf0cd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ar/strings.xml @@ -847,7 +847,6 @@ افتح ملفات تعريف الدردشة اسحب الوصول حفظ الأرشيف - حفظ اللون كشف سيتم إيقاف استلام الملف. رفض @@ -1124,7 +1123,7 @@ السمات بفضل المستخدمين - المساهمة عبر Weblate! قاعدة البيانات لا تعمل بشكل صحيح. انقر لمعرفة المزيد - ألوان السمة + ألوان الواجهة انقر لتنشيط الملف الشخصي. عزل النقل هذه السلسلة ليست رابط اتصال! @@ -1767,4 +1766,94 @@ شكل الصور الشخصية واجهة المستخدم الليتوانية مربع أو دائرة أو أي شيء بينهما. + عنوان الخادم غير متوافق مع إعدادات الشبكة. + إصدار الخادم غير متوافق مع إعدادات الشبكة. + مفتاح خاطئ أو اتصال غير معروف - على الأرجح حُذف هذا الاتصال. + تم تجاوز السعة - لم يتلق المُستلم الرسائل المُرسلة مسبقًا. + خطأ في خادم الوجهة: %1$s + خطأ: %1$s + خادم إعادة التوجيه: %1$s +\nخطأ في الخادم الوجهة: %2$s + خادم إعادة التوجيه: %1$s +\nخطأ: %2$s + تحذير تسليم الرسالة + مشكلات الشبكة - انتهت صلاحية الرسالة بعد عِدة محاولات لإرسالها. + نعم + لحماية عنوان IP الخاص بك، يستخدم التوجيه الخاص خوادم SMP الخاصة بك لتسليم الرسائل. + توجيه الرسائل الخاصة + التوجيه الخاص + غير محمي + السماح بالرجوع إلى إصدار سابق + لا تستخدم التوجيه الخاص. + وضع توجيه الرسائل + لا + عندما يكون IP مخفيًا + لا ترسل رسائل مباشرةً، حتى لو كان خادمك أو خادم الوجهة لا يدعم التوجيه الخاص. + أرسل الرسائل مباشرة عندما لا يدعم الخادم الوجهة الخاص بك أو الخادم الوجهة التوجيه الخاص. + احتياطي توجيه الرسالة + أظهِر حالة الرسالة + احمِ عنوان IP + بدون تور أو VPN، سيكون عنوان IP الخاص بك مرئيًا لخوادم الملفات. + استخدم التوجيه الخاص مع خوادم غير معروفة. + استخدم التوجيه الخاص مع خوادم غير معروفة عندما لا يكون عنوان IP محميًا. + دائمًا + استخدم دائمًا التوجيه الخاص. + الملفات + مطلقًا + سيطلب التطبيق تأكيد التنزيلات من خوادم ملفات غير معروفة (باستثناء .onion أو عند تفعيل وكيل SOCKS). + أرسل الرسائل مباشرة عندما يكون عنوان IP محميًا ولا يدعم الخادم الوجهة لديك التوجيه الخاص. + مُرحلات غير معروفة + خوادم غير معروفة! + بدون تور أو VPN، سيكون عنوان IP الخاص بك مرئيًا لمُرحلات XFTP هذه: +\n%1$s. + أظهِر قائمة الدردشة في نافذة جديدة + ألوان الدردشة + سمة الدردشة + تلقى الرد + أزِل الصورة + تكرار + إعادة تعيين اللون + أرسلت رد + تعيين السمة الافتراضية + النظام + لون تمييز خلفية الشاشة + لون إضافي ثانوي 2 + الإعدادات المتقدمة + جميع أوضاع الألوان + أسود + وضع اللون + داكن + الوضع الداكن + ألوان الوضع الداكن + ملائمة + طاب يومك! + صباح الخير! + صورة خلفية الشاشة + الوضع الفاتح + السمة الملف الشخصي + فاتح + طبّق لِ + ملء + المقياس + لا شيء + توجيه الرسائل الخاصة 🚀 + اجعل محادثاتك تبدو مختلفة! + تلقي الملفات بأمان + واجهة المستخدم الفارسية + إعادة التعيين إلى سمة التطبيق + سمة التطبيق + تأكيد الملفات من خوادم غير معروفة. + إعادة التعيين إلى سمة المستخدم + معلومات قائمة انتظار الخادم: %1$s +\n +\nآخر رسالة تم استلامها: %2$s + تسليم التصحيح + معلومات قائمة انتظار الرسائل + احمِ عنوان IP الخاص بك من مُرحلات المُراسلة التي اختارتها جهات الاتصال الخاصة بك. +\nفعّل في إعدادات *الشبكة والخوادم*. + سمات دردشة جديدة + حدث خطأ أثناء تهيئة WebView. حدّث نظامك إلى الإصدار الجديد. يُرجى التواصل بالمطورين. +\nError: %s + تحسين تسليم الرسائل + مع انخفاض استخدام البطارية. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml index 9308f5886e..df43d1459f 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/base/strings.xml @@ -746,7 +746,7 @@ To protect your IP address, private routing uses your SMP servers to deliver messages. Appearance Customize theme - THEME COLORS + INTERFACE COLORS App version App version: v%s App build: %s @@ -874,6 +874,7 @@ Speaker Headphones Bluetooth + Error initializing WebView. Update your system to the new version. Please contact developers.\nError: %s The next generation of private messaging @@ -1072,6 +1073,9 @@ APP ICON THEMES Profile images + Chat theme + Profile theme + Chat colors MESSAGES AND FILES PRIVATE MESSAGE ROUTING CALLS @@ -1201,6 +1205,7 @@ Incompatible database version Confirm database upgrades Show console in new window + Show chat list in new window Invalid migration confirmation Upgrade and open chat Downgrade and open chat @@ -1396,6 +1401,7 @@ FOR CONSOLE Local name Database ID + Debug delivery Record updated at Sent at Created at @@ -1457,6 +1463,9 @@ Connection direct indirect (%1$s) + Message queue info + none + server queue info: %1$s\n\nlast received msg: %2$s Welcome message @@ -1543,23 +1552,30 @@ When you share an incognito profile with somebody, this profile will be used for the groups they invite you to. + System + Light + Dark System Light Dark SimpleX + Black System Theme + Color mode Dark theme - Save color + Dark mode colors Import theme Import theme error Make sure the file has correct YAML syntax. Export theme to have an example of the theme file structure. Export theme Reset colors + Reset color + App theme Accent Additional accent Secondary @@ -1567,8 +1583,31 @@ Background Menus & alerts Title + Additional accent 2 Sent message + Sent reply Received message + Received reply + Wallpaper background + Wallpaper accent + Remove image + + + Good afternoon! + Good morning! + Scale + Repeat + Fill + Fit + Advanced settings + Reset to app theme + Reset to user theme + Set default theme + Apply to + All color modes + Light mode + Dark mode + You allow @@ -1815,6 +1854,15 @@ Network management More reliable network connection. Lithuanian UI + Private message routing 🚀 + Protect your IP address from the messaging relays chosen by your contacts.\nEnable in *Network & servers* settings. + New chat themes + Make your chats look different! + Safely receive files + Confirm files from unknown servers. + Improved message delivery + With reduced battery usage. + Persian UI seconds 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 c087e0d896..ca9070cbd6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/bg/strings.xml @@ -1150,7 +1150,6 @@ Отмени промените Запази Нулирай цветовете - Запази цвета Вторичен Избери За да започнете нов чат diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml index cd642f8828..e80b2d88ff 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/cs/strings.xml @@ -55,7 +55,6 @@ Aktualizovat nastavení sítě\? Inkognito Váš náhodný profil - Uložit barvu Obnovit barvu Zbarvení Povolujete @@ -1770,4 +1769,6 @@ Kamera a mikrofon SimpleX odkazy jsou v této skupině zakázány. koncovým šifrováním s dokonalým dopředným utajením, odmítnutím a obnovením po vloupání.]]> + Pokročilé nastavení + Všechny barevné režimy \ 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 6f4afec2f3..df264384a5 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/de/strings.xml @@ -400,11 +400,11 @@ Wenn verfügbar Nein Erforderlich - Onion-Hosts werden verwendet, wenn sie verfügbar sind. + Wenn Onion-Hosts verfügbar sind, werden sie verwendet. Onion-Hosts werden nicht verwendet. Für die Verbindung werden Onion-Hosts benötigt. \nBitte beachten Sie: Ohne .onion-Adresse können Sie keine Verbindung mit den Servern herstellen. - Onion-Hosts werden verwendet, wenn sie verfügbar sind. + Wenn Onion-Hosts verfügbar sind, werden sie verwendet. Onion-Hosts werden nicht verwendet. Für die Verbindung werden Onion-Hosts benötigt. Erscheinungsbild @@ -502,7 +502,7 @@ Audio- & Videoanrufe Ihre Anrufe - Über ein Relais verbinden + Immer über ein Relais verbinden Anrufe auf Sperrbildschirm: Akzeptieren Anzeigen @@ -825,7 +825,7 @@ Protokollzeitüberschreitung PING-Intervall TCP-Keep-Alive aktivieren - Zurückkehren + Zurücksetzen Speichern Netzwerkeinstellungen aktualisieren? Die Aktualisierung der Einstellungen wird den Client wieder mit allen Servern verbinden. @@ -842,7 +842,6 @@ Dunkel Design - Farbe speichern Farben zurücksetzen Akzent @@ -1208,7 +1207,7 @@ Wenn Personen eine Verbindung anfordern, können Sie diese annehmen oder ablehnen. Sie werden Ihre damit verbundenen Kontakte nicht verlieren, wenn Sie diese Adresse später löschen. Design anpassen - DESIGN-FARBEN + INTERFACE-FARBEN Fügen Sie die Adresse Ihrem Profil hinzu, damit Ihre Kontakte sie mit anderen Personen teilen können. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet. Alle Ihre Kontakte bleiben verbunden. Es wird eine Profilaktualisierung an Ihre Kontakte gesendet. Erstellen Sie eine Adresse, damit sich Personen mit Ihnen verbinden können. @@ -1386,7 +1385,7 @@ Nach ungelesenen und favorisierten Chats filtern. Das Senden von Empfangsbestätigungen an alle Kontakte in allen sichtbaren Chat-Profilen wird aktiviert. Das Senden von Bestätigungen an %d Kontakte ist deaktiviert - Diese Einstellungen gelten für Ihr aktuelles Profil + Diese Einstellungen gelten für Ihr aktuelles Chat-Profil Sie können in den Kontakt- und Gruppeneinstellungen überschrieben werden. Kontakte Bestätigungen deaktivieren\? @@ -1430,8 +1429,8 @@ An dieses Gruppenmitglied wird eine Verbindungsanfrage gesendet. Direkt verbinden\? Inkognito verbinden - Das aktuelle Profil nutzen - Ein neues Inkognito-Profil nutzen + Aktuelles Chat-Profil nutzen + Neues Inkognito-Profil nutzen App-Akkuverbrauch / Unbeschränkt , um Anrufe im Hintergrund zu führen.]]> Fügen Sie den erhaltenen Link ein, um sich mit Ihrem Kontakt zu verbinden… Es wird ein neues Zufallsprofil geteilt. @@ -1460,7 +1459,7 @@ Datenbank-Ordner öffnen Das Passwort wird in Klartext in den Einstellungen gespeichert, nachdem Sie es geändert oder die App neu gestartet haben. Das Passwort wurde in Klartext in den Einstellungen gespeichert. - Bitte beachten Sie: Die Nachrichten- und Dateirelais sind per SOCKS Proxy verbunden. Anrufe und gesendete Link-Vorschaubilder nutzen eine direkte Verbindung.]]> + Bitte beachten Sie: Die Nachrichten- und Datei-Relais sind per SOCKS-Proxy verbunden. Anrufe und gesendete Link-Vorschaubilder nutzen eine direkte Verbindung.]]> Lokale Dateien verschlüsseln Öffnen Gespeicherte Dateien & Medien verschlüsseln @@ -1529,7 +1528,7 @@ Freigeben Ungültiger Datei-Pfad Sie haben über diese Adresse bereits eine Verbindung beantragt! - Die Konsole in einem neuen Fenster anzeigen + Konsole in einem neuen Fenster anzeigen Von %s werden alle neuen Nachrichten ausgeblendet! Blockiert Fehler bei der Neuverhandlung der Verschlüsselung @@ -1670,7 +1669,7 @@ Ehemaliges Mitglied %1$s Die Ausführung dieser Funktion dauert zu lange: %1$d Sekunden: %2$s Langsame Funktion - Zeige langsame API-Aufrufe an + Langsame API-Aufrufe anzeigen unbekannt Optionen für Entwickler unbekannter Gruppenmitglieds-Status @@ -1729,7 +1728,7 @@ Link-Details werden heruntergeladen Archiv wird heruntergeladen Anwenden - Alle Ihre Kontakte, Unterhaltungen und Dateien werden sicher verschlüsselt und in Daten-Paketen auf die konfigurierten XTFP-Server hochgeladen. + Alle Ihre Kontakte, Unterhaltungen und Dateien werden sicher verschlüsselt und in Daten-Paketen auf die konfigurierten XTFP-Relais hochgeladen. Archivieren und Hochladen Warnung: Das Archiv wird gelöscht.]]> Überprüfen Sie Ihre Internet-Verbindung und probieren Sie es nochmals @@ -1850,4 +1849,94 @@ Profil-Bilder Form der Profil-Bilder Quadratisch, kreisförmig oder irgendetwas dazwischen. + Fehler auf dem Zielserver: %1$s + Fehler: %1$s + Warnung bei der Nachrichtenzustellung + Falscher Schlüssel oder unbekannte Verbindung - höchstwahrscheinlich ist diese Verbindung gelöscht. + Die Server-Version ist nicht mit den Netzwerk-Einstellungen kompatibel. + Kapazität überschritten - der Empfänger hat die zuvor gesendeten Nachrichten nicht empfangen. + Weiterleitungsserver: %1$s +\nFehler auf dem Zielserver: %2$s + Weiterleitungsserver: %1$s +\nFehler: %2$s + Netzwerk-Fehler - die Nachricht ist nach vielen Sende-Versuchen abgelaufen. + Die Server-Adresse ist nicht mit den Netzwerk-Einstellungen kompatibel. + Immer + Privates Routing + Nie + Unbekannte Relais + Ungeschützt + Sie nutzen privates Routing mit unbekannten Servern. + Sie nutzen KEIN privates Routing. + Modus für das Nachrichten-Routing + Ja + Nein + Wenn die IP-Adresse versteckt ist + Fallback für das Nachrichten-Routing + Nachrichtenstatus anzeigen + Herabstufung erlauben + Sie nutzen immer privates Routing. + Nachrichten werden nicht direkt versendet, selbst wenn Ihr oder der Zielserver kein privates Routing unterstützt. + PRIVATES NACHRICHTEN-ROUTING + Nachrichten werden direkt versendet, wenn die IP-Adresse geschützt ist, und Ihr oder der Zielserver kein privates Routing unterstützt. + Nachrichten werden direkt versendet, wenn Ihr oder der Zielserver kein privates Routing unterstützt. + Zum Schutz Ihrer IP-Adresse, wird für die Nachrichten-Auslieferung privates Routing über Ihre konfigurierten SMP-Server genutzt. + Sie nutzen privates Routing mit unbekannten Servern, wenn Ihre IP-Adresse nicht geschützt ist. + IP-Adresse schützen + DATEIEN + Die App wird bei unbekannten Datei-Servern nach einer Download-Bestätigung fragen (außer bei .onion oder wenn ein SOCKS-Proxy aktiviert ist). + Unbekannte Server! + Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für Datei-Server sichtbar sein. + Ohne Tor- oder VPN-Nutzung wird Ihre IP-Adresse für diese XFTP-Relais sichtbar sein: +\n%1$s. + Profil-Design + Schwarz + Farbvariante + Dunkel + Hell + Farbe zurücksetzen + Gesendete Antwort + System + Dunkle Variante + Füllen + Helle Variante + Empfangene Antwort + Bild entfernen + Wiederholen + Skalieren + Default-Design einstellen + Wallpaper-Akzent + Wallpaper-Hintergrund + Anwenden auf + Zusätzlicher Akzent 2 + Erweiterte Einstellungen + Alle Farbvarianten + Chat-Farben + Chat-Design + Passend + Chat-Liste in einem neuen Fenster anzeigen + Guten Nachmittag! + Guten Morgen! + Farben für die dunkle Variante + App-Design + Persische Bedienoberfläche + Auf das Benutzer-spezifische Design zurücksetzen + Fehler bei der Initialisierung von Webview. Aktualisieren Sie Ihr System auf die neue Version. Bitte kontaktieren Sie die Entwickler. +\nFehler: %s + Auf das App-Design zurücksetzen + Dateien von unbekannten Servern bestätigen. + Verbesserte Zustellung von Nachrichten + Gestalten Sie Ihre Chats unterschiedlich! + Neue Chat-Designs + Privates Nachrichten-Routing 🚀 + Schützen Sie Ihre IP-Adresse vor den Nachrichten-Relais , die Ihr Kontakt ausgewählt hat. +\nAktivieren Sie es in den *Netzwerk & Server* Einstellungen. + Dateien sicher empfangen + Mit reduziertem Akkuverbrauch. + Keine Information + Debugging-Zustellung + Nachrichten-Warteschlangen-Information + Server-Warteschlangen-Information: %1$s +\n +\nZuletzt empfangene Nachricht: %2$s \ 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 54512aca34..0c013f0bbc 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/es/strings.xml @@ -220,7 +220,7 @@ conectado directa El contacto permite - predefinido (%s) + predeterminado (%s) Eliminar para todos activado Tus contactos sólo pueden marcar los mensajes para eliminar. Tu podrás verlos. @@ -274,7 +274,7 @@ Chat está parado rol de %s cambiado a %s Cambiar rol - Mediante perfil (por defecto) o por conexión (BETA) + Mediante perfil (predeterminado) o por conexión (BETA) cambiando de servidor… Preferencias de Chat cancelado %s @@ -606,8 +606,7 @@ llamada rechazada secreto Abrir SimpleX Chat para aceptar llamada - Restablecer valores por defecto - Guardar color + Restablecer valores predetarminados Pendiente Notificaciones periódicas Guarda la contraseña de forma segura, NO podrás cambiarla si la pierdes. @@ -965,7 +964,7 @@ Guardar contraseña de perfil Contraseña para hacerlo visible Error al guardar contraseña de usuario - El retransmisor sólo se usa en caso de necesidad. Un tercero podría ver tu IP. + El servidor de retransmisión sólo se usa en caso de necesidad. Un tercero podría ver tu IP. El servidor de retransmisión protege tu IP pero puede ver la duración de la llamada. Introduce la contraseña Ocultar @@ -1162,7 +1161,7 @@ Mensaje enviado Dejar de compartir ¿Dejar de compartir la dirección\? - COLORES DEL TEMA + COLORES DEL INTERFAZ Puedes crearla más tarde ¿Compartir la dirección con los contactos\? Compartir con contactos @@ -1510,7 +1509,7 @@ autor Pegar dirección de ordenador %1$s!]]> - Verificar código con ordenador + Verifica el código en el ordenador Escanear código QR desde ordenador Desbloquear Detectable mediante red local @@ -1740,7 +1739,7 @@ WiFi Ethernet por cable administradores - Activar para + Activado para No permitir el envío de enlaces SimpleX todos los miembros Permitir enviar enlaces SimpleX. @@ -1767,7 +1766,97 @@ Al iniciar llamadas de audio y vídeo. ¡Será habilitado en los chats directos! Conexión de red más fiable. - Imágenes del perfil + Forma de los perfiles Dar forma a las imágenes de perfil Cuadrada, circular o cualquier forma intermedia. + Capacidad excedida - el destinatario no ha recibido los mensajes previos. + Error del servidor de destino: %1$s + Error: %1$s + Servidor de reenvío: %1$s +\nError del servidor de destino: %2$s + Servidor de reenvío: %1$s +\nError: %2$s + Problema en la red - el mensaje ha expirado tras muchos intentos de envío. + La versión del servidor es incompatible con la configuración de red. + Enrutamiento privado + Servidor de retransmisión desconocido + NO usar enrutamiento privado. + Enrutamiento de mensajes alternativo + Modo de enrutamiento de mensajes + No + Estado del mensaje + Usar enrutamiento privado con servidores desconocidos cuando la dirección IP no está protegida. + Con IP oculta + Si + Enviar mensajes directamente cuando tu servidor o el de destino no admitan enrutamiento privado. + Para proteger tu dirección IP, el enrutamiento privado usa tus servidores SMP para enviar mensajes. + NO enviar mensajes directamente incluso si tu servidor o el de destino no soportan enrutamiento privado. + Siempre + Permitir versión anterior + Usar siempre enrutamiento privado. + Aviso de entrega de mensaje + Nunca + ENRUTAMIENTO PRIVADO DE MENSAJES + La dirección del servidor es incompatible con la configuración de la red. + Desprotegido + Clave incorrecta o conexión desconocida - probablemente esta conexión fue eliminada + Usar enrutamiento privado con servidores desconocidos. + Enviar mensajes directamente cuando tu dirección IP está protegida y tu servidor o el de destino no admitan enrutamiento privado. + ¡Servidores desconocidos! + Sin Tor o VPN, tu dirección IP será visible para estos relés XFTP: +\n%1$s. + Proteger dirección IP + Sin Tor o VPN, tu dirección IP será visible para los servidores de archivos. + ARCHIVOS + La aplicación pedirá confirmar las descargas de servidores de archivos desconocidos (excepto .onion o cuando se active SOCKS proxy). + Colores del chat + Tema del chat + Aplicar a + Negro + Todos los modos + Modo de color + Oscuro + Modo oscuro + Colores en modo oscuro + Relleno + ¡Buenas tardes! + ¡Buenos días! + Claro + Modo claro + Respuesta recibida + Mosaico + Quitar imagen + Restablecer color + Escala + Respuesta enviada + Establecer tema predeterminado + Sistema + Color de fondo + Encaje + Color adicional 2 + Configuración avanzada + Tema del perfil + Listado del chat en ventana nueva + Color imagen de fondo + información cola del servidor: %1$s +\n +\núltimo mensaje recibido: %2$s + Restablecer al tema de la app + Enrutamiento privado de mensajes 🚀 + Recibe archivos de forma segura + Mejora del envío de mensajes + Con uso reducido de la batería. + Tema de la app + Confirma archivos de servidores desconocidos. + Entrega de debug + ¡Cambia el aspecto de tus chats! + Nuevos temas de chat + Información cola de mensajes + ninguno + Protege tu dirección IP de los servidores de retransmisión elegidos por tus contactos. +\nActívalo en ajustes de *Servidores y Redes*. + Restablecer al tema del usuario + Error al inicializar WebView. Actualiza tu sistema a la última versión. Por favor, ponte en contacto con los desarrolladores. +\nError: %s + Interfaz en persa \ 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 8ef94b53fe..10daae4c52 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fa/strings.xml @@ -5,8 +5,8 @@ %1$d پیام از قلم افتاده %1$s عضو %1$s می‌خواهد به شما متصل شود، به وسیله - 1 روز - 1 دقیقه + ۱ روز + ۱ دقیقه لغو لغو تغییر نشانی تغییر نشانی را لغو می‌کنید؟ @@ -36,7 +36,7 @@ مسیر نامعتبر پرونده برنامه از کار افتاد در حال تلاش برای اتصال به سرور مورد استفاده برای دریافت پیام‌ها از این مخاطب (خطا: %1$s). - حذف شده + حذف شد علامت گذاشته شده به عنوان حذف شده توسط %s حذف شد مسدود @@ -49,15 +49,15 @@ داده نامعتبر خطا در نمایش محتوا خطا در رمزگشایی - 5 دقیقه + ۵ دقیقه درباره نشانی سیمپل‌اکس(SimpleX) لینک یک بار مصرف درباره سیمپل‌اکس چت(SimpleX Chat) %1$d پیام از قلم افتاد. - 1 ماه - 1 هفته - 6 زبان جدید برای رابط کاربری - 30 ثانیه + ۱ ماه + ۱ هفته + ۶ زبان جدید برای رابط کاربری + ۳۰ ثانیه شما لینک یک بار مصرف ناشناس به اشتراک گذاشتید به وسیله لینک گروه ناشناس به وسیله لینک گروه @@ -229,25 +229,25 @@ توقف دریافت پرونده متوقف خواهد شد. خوش آمدید! - خطا در رمزگشایی + خطا در کدبرداری ارسال پیام مستقیم برای اتصال لطفا، تا زمانی که پرونده در حال بارگیری از موبایل متصل است، منتظر باشید. حذف مخاطب - مشاهده رمز امنیتی - تایید رمز امنیتی + مشاهده کد امنیتی + تایید کد امنیتی ارسال پیام ناپدید شونده لینک دعوت یک‌بارمصرف ایجاد گروه محرمانه (برای اشتراک‌گذاری با مخاطبتان) اگر لینک دعوت SimpleX Chat دریافت کردید، می‌توانید آن را در مرورگر خود باز کنید: - ورود رمز عبور + ورود کد عبور %d ثانیه - رمز عبور فعلی + کد عبور فعلی کپی پیام به عنوان حذف شده علامت‌گذاری خواهد شد. گیرنده‌ها قادر خواهند بود این پیام را آشکار کنند. ارسال غیرموفق برای اتصال لمس کنید - امکان رمزگشایی ویدئو وجود ندارد. لطفا، ویدئوی دیگری را امتحان کنید یا با توسعه‌دهندگان تماس بگیرید. + امکان کدبرداری ویدئو وجود ندارد. لطفا، ویدئوی دیگری را امتحان کنید یا با توسعه‌دهندگان تماس بگیرید. تصویر پرونده پیدا نشد لمس دکمه @@ -278,10 +278,10 @@ تماس تصویری برای محافظت از اطلاعاتتان، قفل SimpleX را روشن کنید. \nاز شما خواسته خواهد شد قبل از فعال شدن این ویژگی، تصدیق را تکمیل کنید. - عدم وجود رمز عبور + عدم وجود کد عبور %d دقیقه فعال‌سازی قفل SimpleX - وارد کردن رمز عبور + وارد کردن کد عبور ذخیره شده فرستاده شده فرستاده شده از @@ -323,7 +323,7 @@ مخاطب و تمام پیام‌ها حذف خواهند شد - این عمل قابل برگشت نیست! پیام‌های صوتی مجازند؟ پیام‌های صوتی ممنوع هستند! - اسکن رمز QR.]]> + اسکن کد QR.]]> اشتراک‌گذاری پرونده… تعداد تصویر بیش از اندازه! برای اسکن لمس کنید @@ -376,7 +376,7 @@ در انتظار تصویر پیام صوتی… تعیین نام مخاطب… - قطع اتصال + قطع شد در حال انتظار نشانی‌ دریافت تغییر کند؟ تغییر نشانی‌ لغو خواهد شد. نشانی‌ دریافت پیشین استفاده خواهد شد. @@ -384,7 +384,7 @@ فقط صاحبان گروه می‌توانند پیام‌های صوتی را فعال کنند. تایید بازنشاندن - اسکن رمز QR + اسکن کد QR (اسکن یا الصاق از حافظه) (تنها ذخیره شده توسط اعضای گروه) فعال کردن دسترسی دوربین @@ -404,7 +404,7 @@ ضبط پیام صوتی ارسال پیام بدون جزئیات - اتصال به وسیله لینک / رمز QR + اتصال به وسیله لینک / کد QR دوربین موجود نیست تصویر ایجاد لینک دعوت یک‌بارمصرف @@ -413,14 +413,14 @@ اتصال از طریق لینک تصویر وقتی دریافت خواهد شد که مخاطبتان آنلاین شود، لطفا صبر کنید یا بعدا بررسی کنید! نوع قفل SimpleX - تصدیق سامانه - تغییر رمز عبور + تصدیق سیستم + تغییر کد عبور در پاسخ به فوری تصدیق دستگاه فعال نیست. زمانی که تصدیق دستگاه را فعال کنید می‌توانید قفل SimpleX را از طریق تنظیمات روشن کنید. اجازه دادن ضمیمه - امکان رمزگشایی تصویر وجود ندارد. لطفا، تصویر دیگری را امتحان کنید یا با توسعه‌دهندگان تماس بگیرید. + امکان کدبرداری تصویر وجود ندارد. لطفا، تصویر دیگری را امتحان کنید یا با توسعه‌دهندگان تماس بگیرید. پرونده‌ها و رسانه ممنوع است! تصویر ارسال شد در انتظار ویدئو @@ -470,7 +470,7 @@ راهنمای مارکداون لینک نامعتبر! اشتراک‌گذاری لینک یک بار مصرف - یا رمز QR را اسکن کنید + یا کد QR را اسکن کنید تلاش مجدد عبارت عبور و صدور پایگاه داده افزودن سرورهای از پیش تنظیم شده @@ -483,7 +483,7 @@ SimpleX Chat را برای ترمینال نصب کنید در GitHub ستاره بزنید همکاری کنید - رمز QR + کد QR یک نمایه تصادفی جدید به اشتراک گذاشته خواهد شد. اتصال از طریق لینک لینک دعوت یک‌بارمصرف @@ -506,11 +506,11 @@ وقتی دستگاه میزبان گروه آنلاین شد، به گروه متصل خواهید شد، لطفا صبر کنید یا بعدا بررسی کنید! لینکی که دریافت کردید را الصاق کنید تا به مخاطبتان متصل شوید… نمایه شما %1$s به اشتراک گذاشته خواهد شد. - برای اتصال، مخاطبتان می‌تواند رمز QR را اسکن یا از لینک در برنامه استفاده کند. - اگر نمی‌توانید ملاقات حضوری داشته باشید، رمز QR را در یک تماس تصویری نمایش دهید، یا لینک را به اشتراک بگذارید. - می‌توانید نشانی‌ خود را به صورت لینک یا رمز QR به اشتراک بگذارید - هر کسی می‌تواند به شما متصل شود. + برای اتصال، مخاطبتان می‌تواند کد QR را اسکن یا از لینک در برنامه استفاده کند. + اگر نمی‌توانید ملاقات حضوری داشته باشید، کد QR را در یک تماس تصویری نمایش دهید، یا لینک را به اشتراک بگذارید. + می‌توانید نشانی خود را به صورت لینک یا کد QR به اشتراک بگذارید - هر کسی می‌تواند به شما متصل شود. وقتی اشخاص درخواست اتصال کنند، شما می‌توانید آن را بپذیرید یا رد کنید. - یا این رمز را نشان دهید + یا این کد را نشان دهید می‌توانید دوباره لینک دعوت را در جزئیات اتصال مشاهده کنید. نگه‌داشتن در حال ایجاد لینک… @@ -520,19 +520,19 @@ سرور از پیش تنظیم شده نشانی‌ سرور نامعتبر! به‌کارگیری برای اتصال‌های جدید - رمز QR نامعتبر - رمز امنیتی نادرست! - رمز امنیتی را از برنامه مخاطبتان اسکن کنید. + کد QR نامعتبر + کد امنیتی نادرست! + کد امنیتی را از برنامه مخاطبتان اسکن کنید. علامت‌گذاری به عنوان تایید شده %s تایید نشده است برای تایید رمزگذاری سرتاسر، روی دستگاه‌های خود، کد را با مخاطبتان مقایسه(یا اسکن) کنید. سرورهای XFTP شما - چگونه + روش استفاده در حال استفاده از سرورهای SimpleX Chat. تنظیم سرورهای ICE می‌خواهد به شما متصل شود! وقتی دستگاه مخاطبتان آنلاین شد، متصل خواهید شد، لطفا صبر کنید یا بعدا بررسی کنید! - رمز QR را در تماس تصویری اسکن کنید، یا مخاطبتان می‌تواند یک لینک دعوت به اشتراک بگذارد.]]> + کد QR را در تماس تصویری اسکن کنید، یا مخاطبتان می‌تواند یک لینک دعوت به اشتراک بگذارد.]]> دعوت استعمال نشده نگه داشته شود؟ نشانی‌ SimpleX شما مارکداون در پیام‌ها @@ -544,7 +544,7 @@ ذخیره سرورها عدم موفقیت آزمایش سرور! عدم موفقیت آزمایش چند سرور: - اسکن رمز QR سرور + اسکن کد QR سرور سرورهای SMP شما سرورهای XFTP از سرورهای SimpleX Chat استفاده شود؟ @@ -557,7 +557,7 @@ نشانی‌ SimpleX پاک‌سازی تایید نشانی‌ سرور از پیش تنظیم شده - رمز امنیتی + کد امنیتی به‌کارگیری از سرور سرورها برای اتصال‌های جدید نمایه گپ فعلی شما سرورها ذخیره شوند؟ @@ -567,17 +567,17 @@ لازم است مخاطبتان آنلاین باشد تا اتصال کامل شود. \nمی‌توانید این اتصال را لغو و مخاطب را حذف کنید (و بعدا با یک لینک جدید امتحان کنید). نشانی‌ SimpleX - این رمز QR یک لینک نیست! + این کد QR یک لینک نیست! راهنمای کاربر.]]> این رشته متن، یک لینک اتصال نیست! - رمزی که اسکن کردید یک رمز QR لینک SimpleX نیست. - اسکن رمز + کدی که اسکن کردید یک کد QR لینک SimpleX نیست. + اسکن کد تصویر نمایه بیشتر - نمایش رمز QR - رمز QR نامعتبر + نمایش کد QR + کد QR نامعتبر وقتی درخواست اتصال شما پذیرفته شد، متصل خواهید شد، لطفا صبر کنید یا بعدا بررسی کنید! - رمز QR را در تماس تصویری نمایش دهید، یا لینک را به اشتراک بگذارید.]]> + کد QR را در تماس تصویری نمایش دهید، یا لینک را به اشتراک بگذارید.]]> نمایه گپ شما ارسال خواهد شد \nبه مخاطبتان اطلاعات بیشتر @@ -613,7 +613,7 @@ استفاده از میزبان‌های onion. را روی «خیر» تنظیم کنید اگر پروکسی SOCKS از آنها پشتیبانی نمی‌کند.]]> سفارشی کردن تم نسخه برنامه - رنگ‌های تم + رنگ‌های رابط کاربری نسخه هسته: v%s simplexmq: v%s (%2s) اعلان‌ها از کار خواهند افتاد تا زمانی که برنامه را دوباره راه‌اندازی کنید @@ -682,4 +682,1175 @@ یک نشانی ایجاد کنید تا اشخاص بتوانند به شما متصل شوند. نمایه شما روی دستگاهتان ذخیره شده و فقط با مخاطبانتان به اشتراک گذاشته می‌شود. سرورهای SimpleX قادر به دیدن نمایه شما نیستند. خطا در ذخیره کردن کلمه عبور کاربر + تماس‌های صوتی و تصویری + تماس‌های شما + همیشه از واسطه استفاده شود + همتا به همتا + متناوب + تماس در حال انتظار + تماس ناموفق + تماس پایان یافت + پاسخ به تماس + فوری + از طریق واسطه + صدا روشن + چرخش دوربین + پیش‌نویس پیام + وقتی برنامه در حال اجراست + تماس صوتی رمزگذاری سرتاسر شده + پذیرفتن + حریم خصوصی شما + حالت قفل + ارسال + کد عبور نادرست + کد عبور + مخاطبان + غیرفعال کردن (نگه‌داشتن مقدارهای جایگزین شده) + فعال کردن (نگه‌داشتن مقدارهای جایگزین شده گروه) + غیرفعال برای همه گروه‌ها + کمک + گپ‌ها + اجرای گپ + گپ در حال اجراست + توقف + حذف تمام پرونده‌ها + اصلاح نام به %s؟ + بعدا از طریق تنظیمات قابل تغییر است. + تماس بی‌پاسخ + هش پیام ناصحیح + هش پیام قبلی متفاوت است. + شناسه پیام ناصحیح + رسیدها فعال شوند؟ + شما + پیام‌ها و پرونده‌ها + تماس‌ها + حالت ناشناس + عبارت عبور پایگاه داده + صدور پایگاه داده + نمایه گپ حذف شود؟ + نام خود را وارد کنید: + ایجاد + روش استفاده از مارکداون + می‌توانید از مارکداون برای آرایش پیام‌ها استفاده کنید: + تماس بی‌پاسخ + تماس پذیرفته + نامتمرکز + نمایه خود را ایجاد کنید + SimpleX چگونه کار می‌کند + اگر SimpleX هیچ شناسه کاربری ندارد، چگونه می‌تواند پیام‌ها را تحویل دهد؟]]> + مطالعه بیشتر در مخزن GitHub ما. + مخزن GitHub ما.]]> + استفاده از گپ + بهترین گزینه برای باتری. شما اعلان‌ها را فقط وقتی دریافت می‌کنید که برنامه در حال اجراست (بدون سرویس پس‌زمینه).]]> + تماس‌ها روی صفحه قفل: + پذیرفتن + سرور واسط از نشانی IP شما محافظت می‌کند، اما سرور می‌تواند مدت تماس را مشاهده کند. + گشودن + رمزگذاری سرتاسر شده + قطع تماس + ویدئو خاموش + ویدئو روشن + صدا خاموش + بلندگو خاموش + بلندگو روشن + در حال اتصال + فعال کردن قفل + کد عبور تعیین شد! + کد عبور تغییر کرد! + تغییر حالت قفل + خودتخریبی + فعال کردن کد عبور خودتخریبی + تغییر حالت خودتخریبی + کد عبور خودتخریبی فعال شد! + کد عبور خودتخریبی + نام نمایشی جدید: + اگر کد عبور خودتخریبی خود را زمان باز کردن برنامه وارد کنید: + تمام اطلاعات برنامه حذف می‌شود. + این تنظیمات برای نمایه فعلی شما هستند + ارسال رسید برای %d مخاطب فعال است + غیرفعال برای همه + فعال برای همه گروه‌ها + غیرفعال کردن (نگه‌داشتن مقدارهای جایگزین شده گروه) + توقف برنامه + تم‌ها + حذف پایگاه داده + پایگاه داده گپ وارد شد + %d پرونده با اندازه کل %s + رمزگذاری سرتاسر دو لایه را ذخیره می‌کنند.]]> + نادیده گرفتن + بلوتوث + وارد کردن پایگاه داده + اشخاص فقط از طریق لینک‌هایی که به اشتراک می‌گذارید می‌توانند به شما متصل شوند. + دریافت شوند و از چه سرورهایی به مخاطبان خود پیام می‌فرستید.]]> + تماس از پیش پایان یافته! + هش پیام ناصحیح + پذیرفتن خودکار تصاویر + ارسال پیش‌نمایش‌های لینک + سیستم + کد عبور برنامه + خاموش + ارسال رسید برای %d گروه فعال است + ارسال رسید برای %d گروه غیرفعال است + حمایت از SIMPLEX CHAT + پروکسی SOCKS + استفاده از کامپیوتر + آرشیو پایگاه داده جدید + آرشیو پایگاه داده قدیمی + خطا در شروع گپ + ایمن در برابر اسپم و سو استفاده + چگونه کار می‌کند + تماس تصویری + گوشی + بلندگو + هدفون‌ها + نسل بعدی پیام‌رسانی خصوصی + پایان یافت + خطا در باز کردن مرورگر + جهت صدور عبارت عبور تعیین کنید + محافظت از صفحه برنامه + شروع مجدد + ویژگی‌های آزمایشی + باز کردن پوشه پایگاه داده + گپ متوقف شده است + خطا در حذف پایگاه داده گپ + خطا در وارد کردن پایگاه داده گپ + شما گپ خود را کنترل می‌کنید! + نمایه، مخاطبان و پیام‌های تحویل داده شده شما روی دستگاهتان ذخیره می‌شوند. + نمایه فقط با مخاطبانتان به اشتراک گذاشته می‌شود. + نام نمایشی نمی‌تواند شامل نویسه‌های فاصله باشد. + ایجاد نمایه + نام نامعتبر! + برجسته + در انتظار تایید… + پاسخ دریافت شد… + تایید دریافت شد… + در حال اتصال… + در انتظار پاسخ… + تعیین عبارت عبور پایگاه داده + استفاده از عبارت عبور تصادفی + تماس صوتی + سرورهای ICE شما + تماس در جریان است + شناسه پیام ناصحیح + حریم خصوصی و امنیت + کد عبور جدید + تصدیق لغو شد + نمایش آخرین پیام‌ها + فعال کردن خودتخریبی + کد عبور برنامه با کد عبور خودتخریبی جایگزین می‌شود. + اگر کد عبور خودتخریبی خود را زمان باز کردن برنامه وارد کنید، تمام اطلاعات برنامه به صورت غیر قابل بازگشت حذف خواهد شد! + کد عبور خودتخریبی تغییر کرد! + رسیدها برای گروه‌ها فعال شوند؟ + فعال برای همه + آن‌ها در تنظیمات مخاطب و گروه قابل جایگزینی هستند. + گروه‌های کوچک (حداکثر ۲۰) + تنظیمات + اولین بن‌سازه بدون هیچ شناسه کاربری - با طرح‌ریزی خصوصی + اعلان‌های خصوصی + عبارت عبور تصادفی در تنظیمات به صورت متن آشکار ذخیره می‌شود. +\nمی‌توانید بعدا آن را تغییر دهید. + تماس تصویری رسیده + تماس صوتی رسیده + تماس تصویری رمزگذاری سرتاسر شده + تماس صوتی (رمزگذاری سرتاسر نشده) + بدون رمزگذاری سرتاسر + مخاطب رمزگذاری سرتاسر دارد + این اتفاق وقتی می‌افتد که شما یا اتصالتان از پشتیبان پایگاه داده قدیمی استفاده کرده باشید. + مذاکره مجدد رمزگذاری ناموفق بود. + رمزگذاری پرونده‌های محلی + پشتیبان‌گیری اطلاعات برنامه + قفل بعد از + تایید کد عبور + کد عبور تغییر نکرد! + ایجاد نمایه + مورب + ما هیچکدام از مخاطبان و پیام‌های(وقتی تحویل داده شدند) شما را روی سرورها ذخیره نمی‌کنیم. + رنگی + محرمانه + در حال تماس… + تماس ناموفق + تماس در جریان است + تماس پایان یافت %1$s + خطا در تماس + در حال راه‌اندازی… + متصل + در حال برقراری تماس… + تماس تصویری (رمزگذاری سرتاسر نشده) + رد کردن + تعیین کد عبور + رسیدها برای گروه‌ها غیرفعال شوند؟ + تغییر کد عبور خودتخریبی + رسیدهای تحویل ارسال شوند به + ابزارهای توسعه‌دهنده + برنامه + دستگاه + آزمایشی + اتصال شبکه + آیکون برنامه + پایگاه داده گپ + بن‌سازه پیام‌رسانی و کاربردی که از حریم خصوصی و امنیت شما محافظت می‌کند. + گزینه خوب برای باتری. سرویس پس‌زمینه هر ۱۰ دقیقه پیام‌ها را بررسی می‌کند. ممکن است تماس‌ها یا پیام‌های ضروری را از دست دهید.]]> + پیام‌ها از قلم افتادند + مرورگر وب پیش‌فرض برای تماس‌ها لازم است. لطفا مرورگر پیش‌فرض را در سیستم تنظیم کنید، و اطلاعات بیشتر را با توسعه‌دهندگان به اشتراک بگذارید. + سرور واسط فقط در زمان نیاز مورد استفاده قرار می‌گیرد. طرف دیگری قادر به مشاهده نشانی IP شما خواهد بود. + نمایش + غیرفعال + گشودن SimpleX Chat برای پذیرفتن تماس + تماس‌ها از صفحه قفل را از طریق تنظیمات فعال کنید. + مخاطب رمزگذاری سرتاسر ندارد + سرورهای WebRTC ICE + شناسه پیام بعدی نادرست است (کمتر یا برابر است با قبلی). +\nبروز این اتفاق می‌تواند به دلیل وجود اشکال نرم‌افزاری یا مورد حمله قرار گرفتن اتصال باشد. + لطفا آن را به توسعه‌دهندگان گزارش دهید. + پیام همسان + گپ متوقف شود؟ + پایگاه داده با استفاده از یک عبارت عبور تصادفی رمزگذاری شده، لطفا پیش از صدور آن را تغییر دهید. + خطا در متوقف کردن گپ + خطا در صدور پایگاه داده گپ + پایگاه داده گپ وارد شود؟ + وارد کردن + به منظور استفاده از پایگاه داده گپ وارد شده، برنامه را شروع مجدد کنید. + چند خطای غیر مهلک هنگام وارد کردن رخ داد - برای اطلاعات بیشتر می‌توانید کنسول گپ را ببینید. + پایگاه داده گپ حذف شد + به منظور ایجاد نمایه گپ جدید، برنامه را شروع مجدد کنید. + پرونده‌ها و رسانه + حذف پرونده‌ها برای تمام نمایه‌های گپ + پرونده‌ها و رسانه حذف شوند؟ + هیچ پرونده دریافتی یا ارسالی وجود ندارد + هرگز + %s ثانیه + اجرای اتصال خصوصی + جابه‌جایی از دستگاهی دیگر + یک نمایه گپ خالی با نام فراهم شده ایجاد می‌شود، و برنامه به طور معمول باز می‌شود. + اعطای اجازه‌ها در تنظیمات + این مجوز را در تنظیمات اندروید پیدا و به صورت دستی آن را اعطا کنید. + باز کردن تنظیمات + پروتکل و کد متن‌باز - هر کسی می‌تواند سرورها را راه‌اندازی کند. + رسیدها غیرفعال شوند؟ + فعال کردن (نگه‌داشتن مقدارهای جایگزین شده) + اعطای اجازه‌ها + میکروفون + دوربین + دوربین و میکروفون + اعطای اجازه‌ها برای برقراری تماس‌ها + حریم خصوصی باز تعریف شده + برای حفاظت از حریم خصوصی، به جای شناسه‌های کاربری مورد استفاده در بن‌سازه‌های دیگر، SimpleX شناسه‌هایی برای صفوف پیام دارد، جدا برای هر کدام از مخاطبان شما. + از باتری بیشتر استفاده می‌کند! سرویس پس‌زمینه همیشه در حال اجراست - اعلان‌ها به محض موجود شدن، نمایش داده می‌شوند.]]> + وقتی می‌تواند اتفاق بیفتد که: +\n۱. پیام‌ها در کلاینت فرستنده بعد از ۲ روز یا روی سرور بعد از ۳۰ روز منقضی شده باشند. +\n۲. رمزگشایی پیام ناموفق بود، چون شما یا مخاطبتان از پشتیبان پایگاه داده قدیمی استفاده استفاده کردید. +\n۳. اتصال مورد حمله قرار گرفته باشد. + تصاویر نمایه + به منظور صدور، ورود و حذف پایگاه داده گپ، گپ را متوقف کنید. هنگامی که گپ متوقف شده است، شما قادر به دریافت و ارسال پیام نخواهید بود. + پایگاه داده گپ فعلی شما حذف و توسط پایگاه داده وارد شده جایگزین خواهد شد. +\nاین عمل قابل برگشت نیست - نمایه، مخاطبان، پیام‌ها و پرونده‌های شما به صورت غیر قابل بازگشت از بین خواهند رفت. + پایگاه داده گپ شما + این عمل قابل برگشت نیست - نمایه، مخاطبان، پیام‌ها و پرونده‌های شما به صورت غیر قابل بازگشت از بین خواهند رفت. + ارسال رسید برای %d مخاطب غیرفعال است + این عمل قابل برگشت نیست - تمام پرونده‌ها و رسانه دریافتی حذف خواهند شد. عکس‌های با کیفیت پایین باقی خواهند ماند. + شما باید از تازه‌ترین نسخه پایگاه داده گپ خود روی فقط یک دستگاه استفاده کنید، در غیر این صورت ممکن است از بعضی از مخاطب‌ها ‌دیگر پیامی دریافت نکنید. + پیام‌ها + به منظور فعال‌سازی اقدامات پایگاه داده، گپ را متوقف کنید. + این عمل قابل برگشت نیست - پیام‌های ارسالی و دریافتی قدیمی‌تر از زمان انتخابی حذف خواهند شد. این کار ممکن است چندین دقیقه زمان ببرد. + خطا در تغییر تنظیمات + ذخیره عبارت عبور در تنظیمات + حذف پیام‌ها + ذخیره عبارت عبور در مخزن کلید + این تنظیمات بر پیام‌های موجود در نمایه گپ فعلی شما اعمال می‌شود + حذف خودکار پیام فعال شود؟ + برگرداندن + ارتقا و گشودن گپ + آرشیو گپ + ذخیره آرشیو + حذف آرشیو + دعوت به گروه %1$s + به گروه می‌پیوندید؟ + ترک + دریافت پیام‌ها از این گروه برای شما متوقف خواهد شد. تاریخچه گپ حفظ خواهد شد. + دعوت اعضا + %d رویداد گروه + %s، %s و %d عضو دیگر متصل شدند + %s و %s + %s، %s و %d عضو + و %d رویداد دیگر + گشودن + کد امنیتی تغییر پیدا کرد + وضعیت ناشناخته + سازنده + عبارت عبور جدید… + پایگاه داده رمزگذاری و عبارت عبور در تنظیمات ذخیره خواهد شد. + عبارت عبور رمزگذاری پایگاه داده به‌روز و در تنظیمات ذخیره خواهد شد. + امکان دسترسی مخزن کلید برای ذخیره کلمه عبور پایگاه داده وجود ندارد + خطای پایگاه داده ناشناخته: %s + تایید جابه‌جایی نامعتبر + شما به این گروه پیوستید + شما ترک کردید + نمایه گروه به‌روز شد + عضو + مدیر + صاحب + حذف شد + نقش عضو جدید + عبارت عبور از تنظیمات پاک شود؟ + لطفا توجه داشته باشید: اگر عبارت عبور را از دست بدهید، قادر نخواهید بود آن را بازیابی کنید یا تغییر دهید.]]> + عبارت عبور پایگاه داده اشتباه + پرونده: %s + عبارت عبور پایگاه داده برای گشودن گپ الزامی است. + خطای ناشناخته + گشودن گپ + تلاش برای تغییر عبارت عبور پایگاه داده کامل نشد. + ارتقای پایگاه داده + تنزل پایگاه داده + تایید ارتقای پایگاه داده + گروه را ترک می‌کنید؟ + امکان دعوت مخاطبان وجود ندارد! + در حال استفاده از نمایه ناشناس برای این گروه هستید - برای جلوگیری از اشتراک‌گذاری نمایه اصلی شما، دعوت مخاطبان مجاز نیست + مخاطب حذف شد + متصل شد + ترک کرد + %1$s حذف شد + شما حذف شدید + توافق رمزگذاری + ناظر + عضو پیشین %1$s + بسط دادن انتخاب نقش + عبارت عبور رمزگذاری پایگاه داده به‌روز خواهد شد. + مستقیما متصل شد + %s متصل شد + پیوستن به گروه + به‌روزرسانی + تایید عبارت عبور جدید… + پایگاه داده رمزگذاری شود؟ + نسخه پایگاه داده ناسازگار + نمایه گروه به‌روز شد + شما نشانی را برای %s تغییر دادید + شما نشانی را تغییر دادید + مذاکره مجدد رمزگذاری مجاز است + گروه حذف شد + در حال اتصال (دعوت معرفی) + رمزگذاری سرتاسر استاندارد + در حال اتصال (پذیرفته شد) + در حال اتصال (اعلام شد) + متصل شد + کامل + در حال اتصال + ناشناخته + دعوت به گروه + %d مخاطب انتخاب شد + امکان دعوت مخاطب وجود ندارد! + پیوستن + گروه غیرفعال + دعوت‌نامه گروه دیگر اعتبار ندارد، توسط فرستنده پاک شد. + گروه پیدا نشد! + شما به گروه دعوت شیده‌اید + شما دعوت گروه را رد کردید + از %1$s دعوت شده + نقش %s به %s تغییر کرد + گروه حذف شد + مذاکره مجدد رمزگذاری الزامی است + رمزگذاری برای %s بی‌عیب است + مذاکره مجدد رمزگذاری برای %s مجاز است + رمزگذاری سرتاسر مقاوم در برابر کوانتوم + مخاطبی برای افزودن وجود ندارد + رد شدن از دعوت اعضا + انتخاب مخاطبان + پایگاه داده رمزگذاری شده! + عبارت عبور فعلی… + عضو %1$s به %2$s تغییر کرد + رمزگذاری + به‌روزرسانی عبارت عبور پایگاه داده + تعیین عبارت عبور + از مخزن کلید اندروید برای ذخیره امن عبارت عبور استفاده می‌شود - به سرویس اعلان اجازه عمل می‌دهد. + باید هر بار که برنامه شروع می‌شود عبارت عبور را وارد کنید - در دستگاه ذخیره نمی‌شود. + پایگاه داده رمزگذاری شده + مخاطب بررسی شد + پاک کردن + نمایش کنسول در پنجره جدید + می‌توانید گپ را از طریق تنظیمات برنامه / پایگاه داده یا با شروع مجدد برنامه شروع کنید. + گپ شروع شود؟ + هشدار: ممکن است بعضی از اطلاعات را از دست بدهید! + گپ متوقف شده است + خطا در رمزگذاری پایگاه داده + عبارت عبور از مخزن کلید پاک شود؟ + اعلان‌ها فقط تا زمان توقف برنامه تحویل داده خواهند شد! + پاک کردن + تعیین عبارت عبور پایگاه داده + لطفا عبارت عبور فعلی درست را وارد کنید. + پایگاه داده گپ شما رمزگذاری نشده است - برای محافظت از آن عبارت عبور تعیین کنید. + عبارت عبور به صورت متن آشکار در تنظیمات ذخیره شده است. + بعد از تغییر عبارت عبور یا شروع مجدد برنامه، عبارت عبور به صورت متن آشکار در تنظیمات ذخیره خواهد شد. + عبارت عبور پایگاه داده تغییر داده شود؟ + پایگاه داده رمزگذاری خواهد شد. + لطفا عبارت عبور را به صورت امن ذخیره کنید، اگر آن را از دست دهید، قادر نخواهید بود به گپ دسترسی پیدا کنید. + خطا در پایگاه داده + خطا در Keychain + عبارت عبور پایگاه داده با آنچه در مخزن کلید ذخیره شده متفاوت است. + خطا: %s + عبارت عبور اشتباه! + ورود عبارت عبور… + ذخیره عبارت عبور و گشودن گپ + برگرداندن پشتیبان پایگاه داده + پشتیبان پایگاه داده برگردانده شود؟ + لطفا بعد از برگرداندن پشتیبان پایگاه داده، کلمه عبور قبلی را وارد کنید. این عمل قابل برگشت نیست. + خطا در برگرداندن پایگاه داده + نسخه پایگاه داده از برنامه جدیدتر است، اما بدون جابه‌جایی تنزلی برای: %s + جابه‌جایی متفاوت در برنامه/پایگاه داده: %s / %s + جابه‌جایی‌ها: %s + آرشیو گپ + ایجاد شده در %1$s + آرشیو گپ حذف شود؟ + شما به گروه دعوت شده‌اید. برای متصل شدن به اعضای گروه، به گروه بپیوندید. + پیوستن به صورت ناشناس + این گروه دیگر وجود ندارد. + شما دعوت گروه ارسال کردید + برای پیوستن لمس کنید + برای پیوستن به صورت ناشناس لمس کنید + %s مسدود شد + مسدود سازی %s لغو شد + نقش شما به %s تغییر کرد + شما نقش خود را به %s تغییر دادید + شما مسدود سازی %s را لغو کردید + %s و %s متصل شدند + %s، %s و %s متصل شدند + نشانی برای شما تغییر داده شد + در حال تغییر نشانی… + در حال تغییر نشانی برای %s… + در حال تغییر نشانی… + رمزگذاری بی‌عیب است + در حال توافق رمزگذاری برای %s… + توافق رمزگذاری برای %s + نویسنده + دعوت شد + در حال اتصال (معرفی شد) + نقش آغازین + مخاطبی انتخاب نشده + در حال توافق رمزگذاری… + ترک کرد + مخاطب %1$s به %2$s تغییر کرد + نشانی مخاطب حذف شد + تعیین نشانی مخاطب جدید + پایگاه داده با استفاده از عبارت عبور تصادفی رمزگذاری شده، می‌توانید آن را تغییر دهید. + بعد از شروع مجدد برنامه یا تغییر عبارت عبور، از مخزن کلید اندروید برای ذخیره امن عبارت عبور استفاده خواهد شد - اجازه دریافت اعلان‌ها را خواهد داد. + پایگاه داده رمزگذاری و عبارت عبور در مخزن کلید ذخیره خواهد شد. + عبارت عبور رمزگذاری پایگاه داده به‌روز و در مخزن کلید ذخیره خواهد شد. + عبارت عبور درست را وارد کنید. + لطفا عبارت عبور را به صورت امن ذخیره کنید، اگر آن را از دست دهید، قادر به تغییرش نخواهید بود. + عبارت عبور در مخزن کلید پیدا نشد، لطفا به صورت دستی آن را وارد کنید. دلیل این اتفاق ممکن است برگرداندن اطلاعات برنامه با استفاده از یک ابزار پشتیبان‌گیری باشد. اگر این طور نیست، لطفا با توسعه دهندگان تماس بگیرید. + تنزل و گشودن گپ + گپ متوقف شده است. اگر از پیش از این پایگاه داده روی دستگاه دیگری استفاده می‌کردید، بهتر است قبل از شروع گپ، آن را برگردانید. + به این گروه پیوستید. در حال اتصال به عضوی از گروه که از شما دعوت کرد. + دعوت منقضی شد! + دعوت گروه منقضی شد + از طریق لینک گروهتان دعوت شد + شما نقش %s را به %s تغییر دادید + شما %s را مسدود کردید + شما %1$s را حذف کردید + عکس نمایه حذف شد + تعیین عکس نمایه جدید + نمایه به‌روز شد + مذاکره مجدد رمزگذاری برای %s الزامی است + در حال ارسال از طریق + اتصال اصلاح شود؟ + اصلاح توسط عضو گروه پشتیبانی نمی‌شود + نمایه گپ شما به اعضای گروه ارسال خواهد شد + ایجاد لینک + خطا در به‌روزرسانی لینک گروه + زمان توقف پروتکل + می‌توانید این نشانی را با مخاطبان خود به اشتراک بگذارید تا به آن‌ها اجازه دهید به %s متصل شوند. + غیرفعال + رسیدها غیرفعال هستند + برای کنسول + حذف + به‌روزرسانی تنظیمات کلاینت را دوباره به سرورها متصل خواهد کرد. + نمایه گپ حذف شود؟ + خصوصی کردن نمایه! + می‌توانید نمایه کاربر را پنهان یا بی‌صدا کنید - برای نمایش منو لمس کنید و‍ نگه دارید. + لغو پنهان‌سازی نمایه + لغو پنهان‌سازی نمایه گپ + کلمه عبور نمایه + نمایه تصادفی شما + ابتدایی + پیام ارسالی + خیر + همیشه + روشن + پیام خوشامدگویی + حذف عضو + سیستم + تم + پیام دریافتی + لینک حذف شود؟ + می‌توانید یک لینک یا کد QR به اشتراک بگذارید - هر کسی می‌تواند به گروه بپیوندد. اگر بعدا گروه را حذف کنید، اعضای گروه را از دست نخواهید داد. + خطا در ایجاد لینک گروه + خطا در ارسال دعوت + اشتراک‌گذاری نشانی + این گروه بیش از %1$d عضو دارد، رسیدهای تحویل ارسال نمی‌شوند. + نام محلی + شناسه پایگاه داده + ایجاد شد در + دریافت شد در: %s + حذف شد در: %s + توسط مدیر حذف شد در: %s + ناپدید می‌شود در: %s + عضو حذف شود؟ + حذف عضو + مسدودسازی عضو + مسدودسازی + عضو برای همه مسدود شود؟ + لغو مسدودسازی + مسدودسازی عضو برای همه لغو شود؟ + مسدود شده توسط مدیر + مسدود + تغییر + تعویض + نقش گروه تغییر داده شود؟ + اتصال مستقیم؟ + درخواست اتصال به این عضو گروه ارسال خواهد شد. + پیام خوشامدگویی ذخیره شود؟ + مذاکره مجدد رمزگذاری + ذخیره نمایه گروه + به‌روزرسانی + شما هنوز تماس‌ها و اعلان‌های نمایه‌های بی‌صدا را وقتی فعال هستند دریافت می‌کنید. + ناشناس + سیستم + وارد کردن تم + شما اجازه می‌دهید + پیش‌فرض (%s) + گروه برای شما حذف خواهد شد - این عمل قابل برگشت نیست! + ترک گروه + لینک گروه + نشانی + در حال دریافت از طریق + بی‌صدا + بازنشاندن رنگ‌ها + اصلاح + شناسه پایگاه داده: %d + رکورد به‌روز شد در: %s + بدون متن + ارسال پیام مستقیم + پنهان کردن + ارسال شد در + روشن + خطا در وارد کردن تم + مخاطب اجازه می‌دهد + شما در حال دعوت از مخاطبی که با او نمایه ناشناسی به اشتراک گذاشته‌اید به گروهی هستید که در آن از نمایه اصلی خود استفاده می‌کنید + حذف گروه + افزودن پیام خوشامدگویی + وضعیت شبکه + نمایه گروه روی دستگاه‌های اعضا ذخیره می‌شود، نه روی سرورها. + اتصال‌های نمایه و سرور + (فعلی) + عضو + عضو از گروه حذف خواهد شد - این عمل قابل برگشت نیست! + مسدود برای همه + اصلاح توسط مخاطب پشتیبانی نمی‌شود + ثانیه + تمام گپ‌ها و پیام‌ها حذف خواهند شد - این عمل قابل برگشت نیست! + فقط اطلاعات نمایه محلی + دعوت اعضا + گروه حذف شود؟ + گروه برای تمام اعضا حذف خواهد شد - این عمل قابل برگشت نیست! + ایجاد لینک گروه + خطا در حذف لینک گروه + دریافت شد در + حذف شد در + توسط مدیر حذف شد در + ایجاد شد در: %s + عضو مسدود شود؟ + ویرایش نمایه گروه + حذف لینک + خطا در ایجاد مخاطب عضو + %s در %s + %s (فعلی) + نقش + ذخیره و به‌روزرسانی نمایه گروه + وقتی نمایه ناشناسی را با کسی به اشتراک می‌گذارید، این نمایه برای گروه‌هایی که شما را به آن‌ها دعوت می‌کند استفاده خواهد شد. + نام گروه را وارد کنید: + ایجاد گروه + خطا در ذخیره نمایه گروه + بازنشاندن به پیش‌فرض‌ها + صدور تم + خطا در حذف عضو + خطا در تغییر نقش + ثانوی + ارسال شد در: %s + %s: %s + پیام ذخیره شده + تغییر نقش + حذف نمایه + شما: %1$s + تمام اعضای گروه متصل باقی خواهند ماند. + تنها صاحبان گروه می‌توانند تنظیمات گروه را تغییر دهند. + رسیدهای ارسال + رکورد به‌روز شد در + ناپدید می‌شود در + تمام پیام‌های %s پنهان خواهند شد! + مسدودسازی عضو لغو شود؟ + لغو مسدودسازی عضو + لغو مسدودسازی برای همه + پیام‌های %s نشان داده خواهند شد! + نقش به «%s» تغییر داده خواهد شد. تمام افراد گروه مطلع خواهند شد. + نقش به «%s» تغییر داده خواهد شد. عضو یک دعوت جدید دریافت خواهد کرد. + خطا در مسدودسازی عضو برای همه + گروه + اتصال + مستقیم + غیرمستقیم (%1$s) + پیام خوشامدگویی + پیام خوشامدگویی بیش از حد طولانی است + پیش‌نمایش + پیام خوشامدگویی را وارد کنید… + پیام بیش از حد بزرگ است + سرورها + تغییر نشانی دریافتی + اصلاح اتصال + ایجاد گروه محرمانه + تماما نامتمرکز - قابل مشاهده فقط توسط اعضا. + نام کامل گروه: + زمان توقف پروتکل در کیلوبایت + دریافت همزمان + زمان توقف اتصال TCP + وقفه پینگ + شمار پینگ + فعال کردن زنده نگه‌داشتن TCP + برگشت + ذخیره + تنظیمات شبکه به‌روزرسانی شود؟ + افزودن نمایه + لغو پنهان‌سازی + لغو بی‌صدا + کلمه عبور را در جستجو وارد کنید + برای فعال‌سازی نمایه لمس کنید. + دوباره نمایش داده نشود + بی‌صدا هنگام غیرفعال بودن! + حذف نمایه گپ + حالت ناشناس از حریم خصوصی شما با استفاده از یک نمایه تصادفی جدید برای هر مخاطب محافظت می‌کند. + اجازه می‌دهد اتصال‌های بی‌نام زیادی داشته باشید بدون اطلاعات مشترک بین آن‌ها در تنها یک نمایه گپ. + تاریک + SimpleX + تم تاریک + مطمئن شوید پرونده دارای ترکیب YAML صحیح است. برای داشتن یک نمونه از ساختار پرونده تم، تم را صادر کنید. + ثانوی اضافی + ابتدایی اضافی + پس‌زمینه + منوها و هشدارها + عنوان + بله + تنظیمات مخاطب + حذف برای همه + به مخاطبان خود اجازه حذف پیام‌های ارسالی به صورت غیرقابل برگشت دهید. (۲۴ ساعت) + به مخاطبان خود اجازه افزودن واکنش‌های پیام می‌دهید. + فقط مخاطب شما می‌توانید پیام‌های ناپدید شونده ارسال کنید. + فقط شما می‌توانید پیام‌ها را به صورت غیرقابل برگشت حذف کنید (مخاطبتان می‌تواند آن‌ها را برای حذف علامت‌گذاری کند). (۲۴ ساعت) + فقط مخاطبتان می‌تواند پیام‌ها را به صورت غیرقابل برگشت حذف کند (شما می‌توانید آن‌ها را برای حذف علامت‌گذاری کنید). (۲۴ ساعت) + فقط شما می‌توانید پیام‌های صوتی ارسال کنید. + هر دوی شما و مخاطبتان می‌توانید واکنش‌های پیام اضافه کنید. + فقط شما می‌توانید واکنش‌های پیام اضافه کنید. + ارسال پیام‌های ناپدید شونده را منع می‌کنید. + اجازه ارسال پیام‌های مستقیم را به اعضا می‌دهید. + اجازه ارسال پرونده‌ها و رسانه را می‌دهید. + اعضای گروه می‌توانند پیام‌های ارسالی را به صورت غیرقابل برگشت حذف کنند. (۲۴ ساعت) + حذف بعد از + تنظیمات گپ + پرونده‌ها و رسانه + تماس‌های صوتی/تصویری + " +\nموجود در v5.1" + به مخاطبان خود اجازه ارسال پیام‌های صوتی می‌دهید. + فقط زمانی اجازه حذف پیام‌ها به صورت غیرقابل برگشت را می‌دهید که مخاطب شما این اجازه را به شما بدهد. (۲۴ ساعت) + پیام‌های ناپدید شونده در این گروه ممنوع هستند. + تعیین تنظیمات گروه + واکنش‌های پیام + حذف پیام به صورت غیرقابل برگشت در این گپ ممنوع است. + %d هفته + به مخاطبان خود اجازه ارسال پیام‌های ناپدید شونده دهید. + فقط وقتی پیام‌های صوتی را مجاز می‌دانید که مخاطب شما آن‌ها را مجاز می‌داند. + واکنش‌های پیام در این گپ ممنوع هستند. + ارسال پیام‌های صوتی را منع می‌کنید. + اعضای گروه می‌توانند پیام‌های صوتی ارسال کنند. + پذیرفتن + هر دوی شما و مخاطبتان می‌توانید پیام‌های ناپدید شونده ارسال کنید. + فقط شما می‌توانید پیام‌های ناپدید شونده ارسال کنید. + حذف پیام به صورت غیرقابل برگشت را منع می‌کنید. + واکنش‌های پیام‌ها را منع می‌کنید. + ارسال ۱۰۰ پیام آخر به اعضای جدید. + تا ۱۰۰ پیام آخر به اعضای جدید ارسال خواهد شد. + %d ماه + %d ماه + %d دقیقه + فقط وقتی تماس‌ها را مجاز می‌دانید که مخاطب شما آن‌ها را مجاز می‌داند. + منع تماس‌های صوتی/تصویری. + ارسال پرونده‌ها و رسانه را منع می‌کنید. + اجازه ارسال لینک‌های SimpleX را می‌دهید. + تاریخچه به اعضای جدید ارسال نمی‌شود. + اعضای گروه می‌توانند واکنش‌های پیام اضافه کنند. + اعضای گروه می‌توانند لینک‌های SimpleX ارسال کنند. + لینک‌های SimpleX در این گروه ممنوع هستند. + %d ثانیه + %d دقیقه + %d ماه + مدیران + صاحبان + جدید در %s + مطالعه بیشتر + هر دوی شما و مخاطبتان می‌توانید پیام‌های صوتی ارسال کنید. + فقط وقتی واکنش‌های پیام را مجاز می‌دانید که مخاطب شما آن‌ها را مجاز می‌داند. + منع واکنش‌های پیام. + فقط زمانی پیام‌های ناپدید شونده را مجاز می‌دانید که مخاطب شما آن‌ها را مجاز بداند. + ارسال پیام‌های ناپدید شونده را منع می‌کنید. + فقط مخاطبتان می‌تواند پیام‌های صوتی ارسال کند. + %d روز + مخاطبان می‌توانند پیام‌ها را برای حذف علامت بگذارند؛ شما قادر به مشاهده آن‌ها خواهید بود. + خاموش` + تنظیمات گروه + تنظیمات شما + پیام‌های ناپدید شونده + پیام‌های مستقیم + پیام‌های صوتی + لینک‌های SimpleX + تاریخچه قابل رویت + فعال + فعال برای شما + فعال برای مخاطب + خاموش + دریافتی، ممنوع + تعیین ۱ روز + منع ارسال پیام‌ها صوتی. + به مخاطبان خود اجازه تماس با شما را می‌دهید. + پیام‌های ناپدید شونده در این گپ ممنوع هستند. + هر دوی شما و مخاطبتان می‌توانید پیام‌ها را به صورتی غیرقابل برگشت حذف کنید. (۲۴ ساعت) + هر دوی شما و مخاطبتان می‌توانید تماس برقرار کنید. + فقط شما می‌توانید تماس برقرار کنید. + فقط مخاطبتان می‌تواند تماس برقرار کند. + تماس‌های صوتی/تصویری ممنوع هستند. + اجازه ارسال پیام‌های ناپدید شونده می‌دهید. + اجازه ارسال پیام‌های صوتی را می‌دهید. + اجازه واکنش‌های پیام را می‌دهید. + ارسال لینک‌های SimpleX را منع می‌کنید + عدم ارسال تاریخچه به اعضای جدید. + اعضای گروه می‌توانند پیام‌های ناپدید شونده ارسال کنند. + اعضای گروه می‌توانند پیام‌های مستقیم ارسال کنند. + پیام‌های مستقیم بین اعضا در این گروه ممنوع هستند. + حذف غیرقابل برگشت در این گروه ممنوع است. + پیام‌های صوتی در این گروه ممنوع هستند. + واکنش‌های پیام در این گروه ممنوع هستند. + اعضای گروه می‌توانند پرونده‌ها و رسانه ارسال کنند. + پرونده‌ها و رسانه در این گروه ممنوع هستند. + %d ثانیه + %d ساعت + %d ساعت + %d ساعت + %d روز + %d روز + %d هفته + %d هفته + پیشنهاد %s + پیشنهاد %s: %2s + لغو %s + تمام اعضا + فعال برای + چی جدید است + پیام‌های صوتی در این گپ ممنوع هستند. + فقط مخاطبتان می‌تواند واکنش‌های پیام اضافه کند. + اجازه حذف پیام‌های ارسالی به صورت غیرقابل برگشت را می‌دهید. (۲۴ ساعت) + ارسال پیام‌های مستقیم به اعضا را منع می‌کنید. + ظرفیت از محدودیت فراتر رفت - گیرنده پیام‌های ارسالی پیشین را دریافت نکرد. + خطای سرور مقصد: %1$s + خطا: %1$s + هم اکنون در حال پیوستن به گروه هستید! + خطای بسیار مهم + اتصال خاتمه یافت + از مسیریابی خصوصی استفاده نشود. + رسیدهای تحویل! + فعال نشود + خطا در فعال‌سازی رسیدهای تحویل! + دستگاه‌ها + اتصال کامپیوتر قطع شود؟اتصال کامپیوتر قطع شود؟ + قطع شد، به دلیل: %s + نسخه برنامه کامپیوتر %s با این برنامه سازگار نیست. + در انتظار کامپیوتر… + اتصال به کامپیوتر + کامپیوترهای متصل + اتصال کامپیوتر قطع شد + %s در وضع بدی است]]> + به خودتان متصل می‌شوید؟ + خطا در بارگیری آرشیو + لغو جابه‌جایی + هشدار: آرشیو حذف خواهد شد.]]> + هم اکنون در حال اتصال هستید! + همیشه + همیشه از مسیریابی خصوصی استفاده شود. + اعمال + کد عبور برنامه + تماس‌های صوتی و تصویری + سرورها را به وسیله اسکن کد QR اضافه کنید. + مدیران می‌توانند یک عضو را برای همه مسدود کنند. + اجازه تنزل + لطفا توجه داشته باشید: به منظور حفاظت امنیت، استفاده از پایگاه داده یکسان در دو دستگاه، رمزگشایی پیام‌های اتصال‌های شما را از کار خواهد انداخت.]]> + جابه‌جایی از دستگاهی دیگر را انتخاب و کد QR را اسکن کنید.]]> + تایید تنظیمات شبکه + تایید کنید که عبارت عبور پایگاه داده رای برای جابه‌جایی آن به خاطر دارید. + کامپیوتر متصل شد + در حال اتصال به کامپیوتر + اتصال متوقف شد + کامپیوتر غیرفعال است + اتصال متوقف شد + دستگاه‌های کامپیوتر + کامپیوتر پیدا شد + به کامپیوتر متصل شد + تنظیمات کامپیوتر متصل + اسکن کد QR از کامپیوتر + کامپیوتر دارای کد دعوت اشتباه است + کامپیوتر دارای نسخه پشتیبانی نشده است. لطفا، اطمینان حاصل کنید که از نسخه یکسان روی هر دو دستگاه استفاده می‌کنید. + از طریق لینک متصل می‌شوید؟ + رسیدهای تحویل غیرفعال هستند! + در حال بارگیری جزئیات لینک + عبارت عبور را وارد کنید + خطا در حذف پایگاه داده + کدهای امنیتی را با مخاطبان خود مقایسه کنید. + برنامه پرونده‌های جدید محلی (به جز ویدئوها) را رمزگذاری می‌کند. + رمزگذاری پرونده‌ها و رسانه ذخیره شده + به وسیله پروتکل امن مقاوم در برابر کوانتوم. + مسدودسازی اعضای گروه + خطا + %s قطع شد، به دلیل: %s]]> + کامپیوتر + رابط چینی و اسپانیایی + به وسیله نمایه گپ (پیش‌فرض) یا به وسیله اتصال (آزمایشی). + در حین اتصال به کامپیوتر، مهلت زمان اتصال تمام شد. + روز‍ + فعال کردن + سفارشی + قطع اتصال + تایید کد با کامپیوتر + خطا در بارگذاری آرشیو + در حال آرشیو پایگاه داده + ایجاد لینک آرشیو + حذف پایگاه داده از این دستگاه + اتصال به کامپیوتر در وضع بدی است + نام‌ها، آواتارها و انزوای ترابری متفاوت. + چند چیز دیگر + کامپیوتر مشغول است + پیام‌های ناپدید شونده + قطع اتصال + پیام‌های بهتر + اتصال به صورت خودکار + قابل کشف از طریق شبکه محلی + سلولی + ایجاد نمایه جدید در برنامه کامپیوتر. 💻 + نشانی کامپیوتر + الصاق نشانی کامپیوتر + یافتن از طریق شبکه محلی + تمام اطلاعات وقتی وارد می‌شوند پاک می‌شوند. + سفارشی کردن و اشتراک‌گذاری تم‌های رنگ. + تم‌های سفارشی + - اتصال به سرویس فهرست راهنما (آزمایشی)! +\n- رسیدهای تحویل (تا ۲۰ دقیقه). +\n- سریع‌تر و پایداری بیشتر. + گروه‌های بهتر + فعال کردن در گپ های مستقیم (آزمایشی)! + اتصال با کامپیوتر قطع شود؟ + به زودی! + بارگیری موفق نبود + گپ جابه‌جا شد! + آرشیو و بارگذاری + تایید بارگذاری + اتصال اینترنت خود را بررسی و دوباره امتحان کنید + برنامه کامپیوتر جدید + وصل کردن برنامه‌های موبایل و کامپیوتر! 🔗 + یافتن و پیوستن به گروه‌ها + ایجاد یک گروه با استفاده از یک نمایه تصادفی. + جابه‌جایی اطلاعات برنامه + استفاده از کامپیوتر را در برنامه موبایل باز و کد QR را اسکن کنید.]]> + در حال بارگیری آرشیو + خطا در صدور پایگاه داده گپ + خطا در ذخیره تنظیمات + تمام مخاطبان، مکالمات و پرونده‌های شما به صورت امن، رمزگذاری و به صورت بسته‌های داده به واسطه‌های XFTP تنظیم شده، بارگذاری خواهند شد. + موبایل متصل شد + مدیران می‌توانند لینک‌ها را برای پیوستن به گروه‌ها ایجاد کنند. + قطع اتصال موبایل‌ها + به موبایل متصل شد + نام این دستگاه را وارد کنید… + (جدید)]]> + پذیرفتن خودکار درخواست‌های مخاطب + نشانی کامپیوتر ناصحیح + نسخه ناسازگار + عربی، بلغاری، فنلاندی، عبری، تایلندی و اوکراینی - با سپاس از کاربران و Weblate + سرور فرستادن: %1$s +\nخطای سرور مقصد: %2$s + سرور فرستادن: %1$s +\nخطا: %2$s + هشدار تحویل پیام + مشکلات شبکه - پیام بعد از تلاش‌های زیاد برای ارسالش منقضی شد. + نشانی سرور با تنظیمات شبکه ناسازگار است. + نسخه سرور با تنظیمات شبکه ناسازگار است. + کلید اشتباه یا اتصال ناشناخته - به احتمال زیاد این اتصال حذف شده است. + لطفا آن را به توسعه‌دهندگان گزارش دهید: +\n%s + نگه‌داشتن اتصال‌های خود + هرگز + استفاده از مسیریابی خصوصی با سرورهای ناشناخته. + استفاده از مسیریابی خصوصی با سرورهای ناشناخته وقتی نشانی IP محافظت نشده است. + حالت مسیریابی پیام + بله + پیام‌ها مستقیما فرستاده نشود، حتی اگر سرور مقصد شما از مسیریابی خصوصی پشتیبانی نکند. + گزینه پس‌رفت مسیریابی پیام + نمایش وضعیت پیام + برای محافظت از نشانی IP شما، مسیریابی خصوصی از سرورهای SMP شما به منظور تحویل پیام‌ها استفاده می‌کند. + مسیریابی پیام خصوصی + مسیریابی خصوصی + تنظیمات سرور بهبودیافته + با پیام خوشامدگویی اختیاری. + مخاطبان شما می‌توانند اجازه حذف کامل پیام را بدهند. + چندین نمایه گپ + نام‌های پرونده خصوصی + پالایش گپ‌های خوانده نشده و برگزیده. + اصلاح رمزگذاری بعد از برگرداندن پشتیبان‌ها. + مدیریت گروه + رابط کاربری ژاپنی و پرتقالی + ناپدید کردن یک پیام + استفاده باتری کاهش یافته + به جای تصدیق سیستم آن را تعیین کنید. + پشتیبانی از بلوتوث و دیگر بهبودها. + - پیام‌های صوتی تا ۵ دقیقه. +\n- زمان سفارشی برای ناپدید کردن. +\n- ویرایش تاریخچه. + پیوستن سریع‌تر و پیام‌های قابل اطمینان تر. + باز فرستادن و ذخیره پیام‌ها + مربع، دایره، و هر چیزی در این بین. + در گپ‌های مستقیم فعال خواهد شد! + ارسال رسیدهای تحویل برای تمام مخاطبان فعال خواهد شد. + می‌توانید بعدا از طریق تنظیمات آن را فعال کنید + نام این دستگاه + تایید کد در موبایل + %s قطع شد]]> + این دستگاه + در انتظار متصل شدن موبایل: + %s مشغول است]]> + تصادفی + تجدید + %s نسخه پشتیبانی نشده دارد. لطفا، اطمینان حاصل کنید که از نسخه یکسان روی هر دو دستگاه استفاده می‌کنید]]> + %s قطع شد]]> + این ویژگی هنوز پشتیبانی نمی‌شود. انتشار بعدی را امتحان کنید. + %1$s هستید.]]> + گروه از قبل وجود دارد! + شما هم اکنون در حال پیوستن به گروه از طریق این لینک هستید. + در حال وارد کردن آرشیو + یا لینک آرشیو را الصاق کنید + الصاق لینک آرشیو + می‌توانید دوباره امتحان کنید. + پرونده حذف شد یا لینک نامعتبر است + جابه‌جایی دستگاه + در حال آماده‌سازی بارگذاری + نهایی‌سازی جابه‌جایی + یا لینک پرونده را به صورت امن به اشتراک بگذارید + شروع گپ + اترنت باسیم + بهبودهای بیشتر به زودی! + سازگار نیست! + تایید اتصال‌ها + %s غیرفعال است]]> + شما از پیش اتصال به وسیله این نشانی را درخواست کرده‌اید! + شروع مجدد گپ + حتی وقتی در مکالمه غیرفعال باشند. + خیر + موبایل متصلی وجود ندارد + مدیران حالا می‌توانند: +\n- پیام‌های اعضا را حذف کنند. +\n- اعضا را غیرفعال کنند ( نقش «ناظر») + لطفا تایید کنید که تنظیمات شبکه برای این دستگاه درست هستند. + محفوظ نگه داشتن پیش‌نویس پیام آخر، به همراه ضمیمه‌ها. + درخواست اتصال تکرار شود؟ + از موبایل اسکن کنید + ارسال رسیدهای تحویل برای تمام مخاطبان در تمام نمایه‌های گپ قابل مشاهده، فعال خواهد شد. + پیام‌ها مستقیما ارسال شود وقتی نشانی IP محافظت می‌شود و سرور مقصد شما از مسیریابی خصوصی پشتیبانی نمی‌کند. + پیام‌ها مستقیما ارسال شود وقتی سرور مقصد شما از مسیریابی خصوصی پشتیبانی نمی‌کند. + شکل دادن به تصاویر نمایه + با سپاس از کاربران - از طریق Weblate همکاری کنید! + با سپاس از کاربران - از طریق Weblate همکاری کنید! + این لینک یک‌بارمصرف خودتان است! + این نشانی‌ SimpleX خودتان است! + واسطه‌های ناشناخته + محافظت نشده + تایید اتصال + تایید عبارت عبور پایگاه داده + وقتی IP پنهان است + شما هم اکنون در حال اتصال از طریق لینک یک‌بارمصرف هستید! + نمایه‌های گپ پنهان + (این دستگاه v%s)]]> + متصل کردن یک موبایل + پیش‌نویس پیام + حداکثر ۴۰ ثانیه، دریافت فوری. + پیام‌های صوتی + پیام‌های ارسال شده بعد زمان تعیین شده حذف خواهند شد. + رابط فرانسوی + به وسیله یک کلمه عبور از نمایه‌های گپ خود محافظت کنید! + بهبودهای بیشتر به زودی! + با سپاس از کاربران - از طریق Weblate همکاری کنید! + رسیدهای تحویل پیام! + %s به پایان رسید]]> + لطفا آن را به توسعه‌دهندگان گزارش دهید: +\n%s +\n +\nپیشنهاد می‌شود که برنامه را شروع مجدد کنید. + رابط ایتالیایی + - تحویل پیام پایدارتر +\n- گروه‌های کمی بهتر +\n- و بیشتر! + کد عبور خودتخریبی + با سپاس از کاربران - از طریق Weblate همکاری کنید! + ویدئوها و پرونده‌ها تا ۱ گیگابایت + تایید عبارت عبور + لینک نامعتبر + هنگام برقراری تماس‌های صوتی و تصویری. + صداهای تماس + منبع پیام خصوصی باقی خواهد ماند. + رابط کاربری لیتوانی + دقیقه + اتصال شبکه پایدارتر. + مدیریت شبکه + هفته + ماه + انتخاب + دستگاه موبایل جدید + در حال متوقف کردن گپ + بدون اتصال شبکه + دیگر + تایید امنیت اتصال + در حال آماده‌سازی بارگیری + جابه‌جایی کامل شد + نباید از یک پایگاه داده روی دو دستگاه استفاده کنید.]]> + پیام خوشامدگویی گروه + - مطلع کردن اختیاری مخاطبان حذف شده. +\n- نام‌های نمایه شامل فاصله. +\n- و بیشتر! + رابط کاربری مجارستانی و ترکی + جابه‌جایی به دستگاه دیگر از طریق کد QR. + گشودن گروه + WiFi + تماس‌های تصویر در تصویر + استفاده از برنامه در حین مکالمه. + در حال جابه‌جایی + %1$s!]]> + امنیت و حریم خصوصی بهبودیافته + پنهان کردن صفحه برنامه در برنامه‌های اخیر. + پیام‌های زنده + گیرنده‌ها به‌روزرسانی‌ها را هم‌زمان با تایپ کردن شما مشاهده می‌کنند. + برای محافظت از منطقه زمانی، پرونده‌های تصویر/صدا از UTC استفاده می‌کنند. + سریع و بدون منتظر ماندن تا زمانی که فرستنده آنلاین شود. + کاهش بیشتر استفاده باتری + تعیین پیام نمایش داده شده به اعضای جدید! + رابط لهستانی + با سپاس از کاربران - از طریق Weblate همکاری کنید! + واکنش‌های پیام + بالاخره، ما آن‌ها را داریم! 🚀 + تیک دومی که ما نداشتیم! ✅ + برای پنهان کردن پیام‌های ناخواسته. + یادداشت‌های خصوصی + با پرونده‌ها و رسانه رمزگذاری شده. + تحویل پیام بهبود یافته + ساعت + می‌توانید بعدا از طریق تنظیمات حریم خصوصی و امنیت برنامه آن‌ها را فعال کنید. + گشودن پورت در فایروال + %s مفقود است]]> + برای اجازه دادن به برنامه موبایل به کامپیوتر متصل شوید، این پورت را در فایروال خود باز کنید، اگر فعال است + خطای داخلی + جابه‌جایی به اینجا + %1$s هستید.]]> + تکرار بارگیری + وارد کردن ناموفق بود + تکرار وارد کردن + نهایی‌سازی جابه‌جایی در دستگاه دیگر. + برای ادامه دادن، گپ باید متوقف شود. + تکرار بارگذاری + %s بارگذاری شد + بارگذاری ناموفق بود + در حال بارگذاری آرشیو + می‌توانید دوباره امتحان کنید. + خطا در تایید عبارت عبور: + ارزیابی امنیت + گروه‌های ناشناس + حالت ناشناس ساده‌شده + تغییر حالت ناشناس هنگام اتصال. + پیوستن به مکالمات گروه + جهت اتصال لینک را الصاق کنید + تاریخچه اخیر و روبات فهرست راهنمای بهبودیافته. + نوار جستجو لینک‌های دعوت قبول می‌کند. + با استفاده باتری کاهش یافته. + ثانیه + رمزگذاری مقاوم در برابر کوانتوم + گروه‌های امن‌تر + تنها یک دستگاه در هر زمان می‌تواند مورد استفاده قرار گیرد + درخواست پیوستن تکرار شود؟ + به گروه خود می‌پیوندید؟ + %s بارگیری شد + پرونده صادر شده وجود ندارد + جابه‌جایی به دستگاه دیگر + هشدار: شروع گپ روی چندین دستگاه پشتیبانی نمی‌شود و باعث عدم موفقیت در تحویل پیام خواهد شد + نام دستگاه با کلاینت موبایل متصل شده به اشتراک گذاشته خواهد شد. + پیدا کردن سریع‌تر گپ‌ها + لینک‌های گروه + حذف غیرقابل برگشت پیام + امنیت SimpleX Chat به وسیله Tails of Bits مورد سنجش قرار گرفت. + انزوای ترابری + کد نشست + %1$s هستید.]]> + موبایل‌های متصل + سرورهای ناشناخته! + حفاظت از نشانی IP + برنامه از شما خواهد خواست تا بارگیری‌ها از سرورهای پرونده ناشناخته را تایید کنید (به جز .onion یا وقتی پروکسی SOCKS فعال است). + پرونده‌ها + بدون تور یا VPN، نشانی IP شما برای سرورهای پرونده قابل رویت خواهد بود. + بدون تور یا VPN، نشانی IP شما برای این واسطه‌های XFTP قابل رویت خواهد بود: +\n%1$s. + هیچ + تحویل پیام بهبود یافته + رابط کاربری فارسی + با استفاده باتری کاهش یافته. + اشکال‌زدایی تحویل + اطلاعات صف پیام + تم برنامه + تایید پرونده‌ها از سرورهای ناشناخته. + اطلاعات صف سرور: %1$s +\n +\nآخرین پیام دریافتی: %2$s + نمایش فهرست گپ در پنجره جدید + حالت تاریک + سیاه + حالت رنگ + تاریک + رنگ‌های حالت تاریک + روشن + بازنشاندن رنگ + سیستم + خطا در مقداردهی اولیه WebView. سیستم خود را به نسخه جدید به روز کنید. لطفا با توسعه‌دهنگان تماس بگیرید. +\nخطا: 9%s + رنگ‌های گپ + تم گپ + تم نمایه + پس‌زمینه کاغذدیواری + ابتدایی اضافی ۲ + تنظیمات پیشرفته + پر کردن + گنجاندن + عصر به خیر! + صبح به خیر! + پاسخ دریافتی + حذف تصویر + تکرار + مقیاس + پاسخ ارسالی + ابتدایی کاغذدیواری + تعیین تم پیش‌فرض + بازنشاندن به تم برنامه + بازنشاندن به تم کاربر + تمام حالت‌های رنگ + اعمال بر + حالت روشن + مسیریابی پیام خصوصی 🚀 + ظاهر گپ‌های خود را متمایز کنید! + تم‌های جدید گپ + از نشانی IP خود در برابر واسطه‌های پیام‌رسانی انتخاب شده توسط مخاطبانتان محافظت کنید. +\nدر تنظیمات «شبکه و سرورها» فعال کنید. + دریافت امن پرونده‌ها \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml index 1434963263..62a01fdd64 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fi/strings.xml @@ -873,7 +873,6 @@ Kiitos käyttäjille – osallistu Weblaten kautta! Profiilin salasana Oletusvärit - Tallenna väri Estä ääniviestien lähettäminen. Lähetetyt viestit poistetaan asetetun ajan kuluttua. Aloita uusi keskustelu 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 77f0abbf2d..a9d9f0188b 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/fr/strings.xml @@ -783,7 +783,6 @@ Clair Sombre Thème - Enregistrer la couleur Réinitialisation des couleurs Principale Vous autorisez @@ -1121,7 +1120,7 @@ Vous ne perdrez pas vos contacts si vous supprimez votre adresse ultérieurement. Adresse SimpleX Vous pouvez accepter ou refuser les demandes de contacts. - COULEURS DU THÈME + COULEURS DE L\'INTERFACE Vos contacts resteront connectés. Partager l\'adresse avec vos contacts \? Partager avec vos contacts @@ -1769,4 +1768,94 @@ Carré, circulaire, ou toute autre forme intermédiaire. Lors des appels audio et vidéo. Interface utilisateur en lituanien + Avertissement sur la distribution des messages + L\'adresse du serveur est incompatible avec les paramètres du réseau. + La version du serveur est incompatible avec les paramètres du réseau. + Toujours utiliser le routage privé. + Ne pas utiliser de routage privé. + Utiliser le routage privé avec des serveurs inconnus lorsque l\'adresse IP n\'est pas protégée. + Envoyez les messages de manière directe lorsque votre serveur ou le serveur de destination ne prend pas en charge le routage privé. + Envoyer les messages de manière directe lorsque l\'adresse IP est protégée et que votre serveur ou le serveur de destination ne prend pas en charge le routage privé. + Problèmes de réseau - le message a expiré après plusieurs tentatives d\'envoi. + Clé erronée ou connexion non identifiée - il est très probable que cette connexion soit supprimée. + Serveur de transfert : %1$s +\nErreur au niveau du serveur de destination : %2$s + Serveur de transfert : %1$s +\nErreur : %2$s + Toujours + Routage privé + Autoriser la rétrogradation + Mode de routage des messages + Jamais + Relais inconnus + Non + Lorsque l\'IP est masquée + Oui + Rabattement du routage des messages + Afficher le statut du message + Protection de l\'adresse IP + FICHIERS + ROUTAGE PRIVÉ DES MESSAGES + Erreur au niveau du serveur de destination : %1$s + Erreur : %1$s + Capacité dépassée - le destinataire n\'a pas pu recevoir les messages envoyés précédemment. + L\'app demandera une confirmation pour les téléchargements depuis des serveurs de fichiers inconnus (sauf .onion ou lorsque le proxy SOCKS est activé). + Ne pas envoyer de messages directement, même si votre serveur ou le serveur de destination ne prend pas en charge le routage privé. + Pour protéger votre adresse IP, le routage privé utilise vos serveurs SMP pour délivrer les messages. + Non protégé + Serveurs inconnus ! + Sans Tor ou un VPN, votre adresse IP sera visible par les serveurs de fichiers. + Utiliser le routage privé avec des serveurs inconnus. + Sans Tor ou un VPN, votre adresse IP sera visible par ces relais XFTP : +\n%1$s. + Accentuation supplémentaire 2 + Paramètres avancés + Noir + Appliquer à + Debug de la distribution + Remplir + Bonjour Alice ! + Amélioration de la transmission des messages + Donnez à vos discussions un style différent ! + Info sur la file des messages + Nouveaux thèmes de discussion + aucun + Routage privé des messages 🚀 + Protégez votre adresse IP des relais de messagerie choisis par vos contacts. +\nActivez-le dans les paramètres *Réseau et serveurs*. + Réinitialiser au thème de l\'utilisateur + Afficher la liste des chats dans une nouvelle fenêtre + Teinte du fond d\'écran + Fond d\'écran + info sur la file du serveur : %1$s +\n +\ndernier message reçu : %2$s + Thème de l\'app + Mode de couleur + Sombre + Couleurs du mode sombre + Salut Bob ! + Réponse reçue + Retirer l\'image + Réinitialiser la couleur + Réponse envoyée + Répéter + Dimension + Tous les modes de couleur + Mode sombre + Adapter + Mode clair + Réinitialiser au thème de l\'app + Définir le thème par défaut + Confirmer les fichiers provenant de serveurs inconnus. + UI en persan + Réception de fichiers en toute sécurité + Consommation réduite de la batterie. + Couleurs de la discussion + Thème de la discussion + Thème de profil + Clair + Système + Erreur d\'initialisation de WebView. Mettez votre système à jour avec la nouvelle version. Veuillez contacter les développeurs. +\nErreur : %s \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml index 56dfb51e2e..61b9ad8de6 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hi/strings.xml @@ -69,7 +69,6 @@ स्वागत %1$s! शुरुआत भेजना - रंग बचाओ साझा करना अस्वीकार आवश्यक diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml index e3a1f124aa..94db4f4618 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/hu/strings.xml @@ -6,7 +6,7 @@ %1$s TAG 1 hónap 1 hét - 6 új felületi nyelv + 6 új kezelőfelületi nyelv 5 perc 1 perc A SimpleX azonosítóról @@ -26,7 +26,7 @@ Elfogadás gombra fent, majd: Elfogadás inkognítóban - Kapcsolatfelvétel elfogadása? + Kapcsolódási kérelem elfogadása? Elfogadás Elfogadás 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. @@ -35,9 +35,9 @@ Csoporttagok letiltása Hitelesítés Egy üres csevegési profil jön létre a megadott névvel, és az alkalmazás a szokásos módon megnyílik. - megszakítva %s + %s visszavonva Előre beállított kiszolgálók hozzáadása - A hang- és videóhívások le vannak tiltva. + A hívások kezdeményezése le van tiltva ebben a csevegésben. Külön TCP kapcsolat (és SOCKS bejelentkezési adatok) lesz használva minden ismerős és csoporttag számára. \nFigyelem: 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. hivatkozás előnézet visszavonása @@ -52,15 +52,15 @@ Ismerőseivel kapcsolatban marad. A profil változtatások frissítésre kerülnek az ismerősöknél. A csevegési profil által (alap beállítás), vagy a kapcsolat által (BÉTA). Egy új véletlenszerű profil lesz megosztva. - Hangüzenetek küldésének engedélyezése kizárólag abban az esetben, ha az ismerőse is engedélyezi. + A hangüzenetek küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. Az alkalmazás build száma: %s Hang-/videóhívások Speciális hálózati beállítások - Hangüzenetek küldésének engedélyezése az ismerősei számára. + A hangüzenetek küldése engedélyezve van az ismerősei számára. Hang- és videóhívások Az alkalmazás titkosítja a helyi fájlokat (a videók kivételével). Hívás fogadása - Eltűnő üzenetek engedélyezése az ismerősei számára. + Az eltűnő üzenetek küldésének engedélyezése az ismerősei számára. Kapcsolódás folyamatban! Nem lehet fogadni a fájlt Hitelesítés elérhetetlen @@ -78,13 +78,13 @@ Cím Csatlakozás folyamatban! Automatikus elfogadás - A háttérszolgáltatás mindig fut - az értesítések azonnal megjelennek, amint üzenetek vannak. + A háttérszolgáltatás mindig fut - az értesítések megjelennek, amint az üzenetek elérhetővé válnak. Az elküldött üzenetek végleges törlése engedélyezve van. (24 óra) Mindkét fél küldhet hangüzeneteket. Téves üzenet ID - Ismerősök általi üzenetreakciók küldésének engedélyezése. + Az üzenetreakciók küldése engedélyezve van az ismerősei számára. A hangüzenetek küldése engedélyezve van. - Üzenetreakciók engedélyezése kizárólag abban az esetben, ha az ismerőse is engedélyezi. + Az üzenetreakciók küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. Vissza Kikapcsolható a beállításokban – az értesítések továbbra is megjelenítésre kerülnek amíg az alkalmazás fut.]]> Az adminok hivatkozásokat hozhatnak létre a csoportokhoz való csatlakozáshoz. @@ -92,7 +92,7 @@ titkosítás elfogadása… Ismerősök meghívása le van tiltva! téves üzenet ID - Ismerős jelölések automatikus elfogadása + Kapcsolódási kérelmek automatikus elfogadása Figyelem: NEM fogja tudni helyreállítani, vagy megváltoztatni a jelmondatot abban az esetben, ha elveszíti.]]> hívás… További másodlagos @@ -100,15 +100,15 @@ Az üzenetreakciók küldése engedélyezve van. Fájl előnézet visszavonása Minden csoporttag kapcsolatban marad. - Több akkumulátort használ! Háttérszolgáltatás mindig fut - az értesítések megjelennek, amint az üzenetek elérhetővé válnak.]]> + Több akkumulátort használ! A háttérszolgáltatás mindig fut - az értesítések megjelennek, amint az üzenetek elérhetővé válnak.]]> Letiltás admin Fénykép előnézet visszavonása A jelkód megadása után minden adat törlésre kerül. Felkérték a videó fogadására - Tag letiltása + Letiltás Még néhány dolog - Hitelesítés megszakítva + Hitelesítés visszavonva A fájlok- és a médiatartalom küldése engedélyezve van. Minden csevegés és üzenet törlésre kerül - ez a művelet nem vonható vissza! hanghívás @@ -122,22 +122,22 @@ Engedélyezés Minden ismerősével kapcsolatban marad. Élő csevegési üzenet visszavonása - Ü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) + Az üzenetek végleges törlése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. (24 óra) Hang- és videóhívások téves üzenet hash - Mindig bekapcsolva + Mindig fut Az Android Keystore biztonságosan fogja tárolni a jelmondatot az alkalmazás újraindítása, vagy a jelmondat megváltoztatás után - lehetővé téve az értesítések fogadását. Minden alkalmazásadat törölve. - Legjobb akkumulátoridő. Csak akkor kap értesítést, ha az alkalmazás fut (NINCS háttérszolgáltatás).]]> + Legjobb akkumulátoridő. Csak akkor kap értesítéseket, amikor az alkalmazás meg van nyitva. (NINCS háttérszolgáltatás.)]]> Megjelenés Az akkumulátor optimalizálá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. - Tag letiltása? + Biztosan letiltja? %1$s hívása befejeződött Jó akkumulátoridő. 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.]]> szerző - Elküldött üzenetek végleges törlésének engedélyezése az ismerősei számára. (24 óra) + Az elküldött üzenetek végleges törlése engedélyezve van az ismerősei számára. (24 óra) Mégse - Az alkalmazás csak akkor tud értesítéseket fogadni amikor fut, háttérszolgáltatás nem indul el + Az alkalmazás csak akkor tud értesítéseket fogadni, amikor meg van nyitva. A háttérszolgáltatás nem indul el. Jobb üzenetek A cím módosítása megszakad. A régi fogadási cím kerül felhasználásra. Engedélyezés @@ -150,21 +150,21 @@ A Keystore-hoz nem sikerül hozzáférni az adatbázis jelszó mentése végett hívás folyamatban Fotók automatikus elfogadása - Hívások engedélyezése az ismerősei számára. + A hívások kezdeményezése engedélyezve van az ismerősei számára. ALKALMAZÁS IKON Kiszolgáló hozzáadása QR-kód beolvasásával. Az eltűnő üzenetek küldése engedélyezve van. - Eltűnő üzenetek engedélyezése kizárólag abban az esetben, ha az ismerőse is engedélyezi. + Az eltűnő üzenetek küldése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. Hang kikapcsolva A közvetlen üzenetek küldése a tagok számára engedélyezve van. Alkalmazás Hívás folyamatban Mindkét fél küldhet üzenetreakciókat. - Mindkét fél tud hívásokat indítani. + Mindkét fél tud hívásokat kezdeményezni. Sikertelen hitelesítés Minden %s által írt új üzenet elrejtésre kerül! Alkalmazás verzió: v%s - Hívások engedélyezése kizárólag abban az esetben, ha az ismerőse is engedélyezi. + A hívások kezdeményezése kizárólag abban az esetben van engedélyezve, ha az ismerőse is engedélyezi. Kiszolgáló hozzáadása… Hang bekapcsolva hanghívás (nem e2e titkosított) @@ -200,7 +200,7 @@ az ismerős e2e titkosítással rendelkezik Csoport létrehozása véletlenszerű profillal. Az ismerős és az összes üzenet törlésre kerül - ez a művelet nem vonható vissza! - Az ismerősök törlésre jelölhetnek üzeneteket ; megtekintheti őket. + Az ismerősei törlésre jelölhetnek üzeneteket; ön majd meg tudja nézni azokat. Kapcsolódás egyszer használatos hivatkozással? Kapcsolódás egy hivatkozás / QR-kód által Kapcsolódási hiba (AUTH) @@ -291,7 +291,7 @@ ICE kiszolgálók beállítása Csoport törlése Hitelesítés törlése - szerző + készítő Megerősítés Törlés nálam %d üzenet törlése? @@ -348,9 +348,9 @@ Üzenet törlése? Függő kapcsolatfelvételi kérések törlése? Adatbázis titkosítva! - Üzenetek törlése? + Üzenetek kiürítése? Visszatérés a korábbi adatbázis verzióra - Üzenetek törlése + Üzenetek kiürítése Adatbázis titkosítási jelmondat frissítve lesz. Kapcsolódás automatikusan Adatbázis hiba @@ -364,8 +364,8 @@ Eszközhitelesítés kikapcsolva. SimpleX zárolás kikapcsolása. Letiltás Letiltás minden csoport számára - Engedélyezés minden csoport részére - engedélyezve ismerős részére + Engedélyezés minden csoport számára + engedélyezve az ismerős számára Az eltűnő üzenetek küldése le van tiltva ebben a csoportban. Azonosító törlése %d hét @@ -408,7 +408,7 @@ %d ismerős kiválasztva Engedélyezés %dhónap - Ebben a csoportban tiltott a tagok közötti közvetlen üzenetek küldése. + A közvetlen üzenetek küldése a tagok között le van tiltva ebben a csoportban. %d perc Az adatbázis egy véletlenszerű jelmondattal van titkosítva. Exportálás előtt változtassa meg a jelmondatot. Üzenet kézbesítés jelentéseket letiltása a csoportok számára? @@ -453,7 +453,7 @@ Látható helyi hálózaton Ne engedélyezze Archívum törlése - Az eltűnő üzenetek le vannak tiltva ebben a csevegésben. + Az eltűnő üzenetek küldése le van tiltva ebben a csevegésben. alap (%s) duplikálódott üzenet Számítógép leválasztása? @@ -462,7 +462,7 @@ %d fájl %s összméretben Adatbázis jelmondat szükséges a csevegés megnyitásához. %dnap - Engedélyezés mindenki részére + Engedélyezés mindenki számára Kézbesítési jelentések kikapcsolva! Kibontás Hiba az üzenet küldésekor @@ -521,7 +521,7 @@ Kísérleti funkciók Engedélyezés (felülírások megtartásával) Helyes jelmondat bevitele. - A csoport törlésre kerül az ön részére - ez a művelet nem vonható vissza! + A csoport törlésre kerül az ön számára - ez a művelet nem vonható vissza! Adatbázis titkosítása? A zárolási képernyőn megjelenő hívások engedélyezése a Beállításokban. titkosítás egyeztetve @@ -565,7 +565,7 @@ Hiba a tag(-ok) hozzáadásakor Fájl A csoport tagjai küldhetnek fájlokat és médiatartalmakat. - Törlés miután + Törlés ennyi idő után Hiba a beállítás megváltoztatásakor Hiba a csoport hivatkozás frissítésekor a csoport törölve @@ -679,7 +679,7 @@ A kép akkor érkezik meg, amikor a küldője befejezte annak feltöltését. QR kód beolvasásával.]]> Kapott SimpleX Chat meghívó hivatkozását megnyithatja böngészőjében: - Ha az alkalmazás megnyitásakor az önmegsemmisítő jelkódot megadásra kerül: + Ha az alkalmazás megnyitásakor megadja az önmegsemmisítő jelkódot: Megtalált számítógép Számítógépek A markdown használata @@ -690,7 +690,7 @@ Elutasítás esetén a feladó NEM kap értesítést. Szerepkör kiválasztásának bővítése A kép akkor érkezik meg, amikor a küldője elérhető lesz, várjon, vagy ellenőrizze később! - meghívta + meghíva Érvénytelen kapcsolati hivatkozás Némítás nincsenek részletek @@ -724,15 +724,15 @@ Társítsa össze a mobil és az asztali alkalmazásokat! 🔗 közvetett (%1$s) Hamarosan további fejlesztések érkeznek! - Az üzenetreakciók ebben a csevegésben le vannak tiltva. + Az üzenetreakciók küldése le van tiltva ebben a csevegésben. Helytelen biztonsági kód! Ez akkor fordulhat elő, ha ön, vagy az ismerőse régi adatbázis biztonsági mentést használt. Új asztali alkalmazás! Most már az adminok is: \n- törölhetik a tagok üzeneteit. \n- letilthatnak tagokat (\"megfigyelő\" szerepkör) - meghívta %1$s-t - Ebben a csoportban az üzenetreakciók le vannak tiltva. + meghívta őt: %1$s + Az üzenetreakciók küldése le van tiltva ebben a csoportban. Nem nincs szöveg TAG @@ -754,7 +754,7 @@ Új kapcsolattartási kérelem Csatlakozás a csoporthoz Összekapcsolt számítógép beállítások - meghívta a csoport hivatkozásán keresztül + meghíva az ön csoport hivatkozásán keresztül elhagyta a csoportot Összekapcsolt számítógépek Nincs alkalmazás jelkód @@ -763,8 +763,8 @@ (csak a csoporttagok tárolják) Moderálás be - Japán és Portugál kezelőfelület - Ebben a csoportban az üzenetek végleges törlése le van tiltva. + Japán és portugál kezelőfelület + Az üzenetek végleges törlése le van tiltva ebben a csoportban. Onion kiszolgálók nem lesznek használva. %s eszközzel megszakadt a kapcsolat]]> hónap @@ -774,7 +774,7 @@ Egyszerre csak 10 videó küldhető el Csak ön adhat hozzá üzenetreakciókat. elhagyta a csoportot - Ebben a csevegésben az üzenetek végleges törlése le van tiltva. + Az üzenetek végleges törlése le van tiltva ebben a csevegésben. Max 40 másodperc, azonnal fogadható. inkognitó a kapcsolattartási azonosító-hivatkozáson keresztül A kapcsolódáshoz Onion kiszolgálókra lesz szükség. @@ -785,7 +785,7 @@ Összekapcsolt mobil eszközök 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. Az üzenet törlésre lesz jelölve. A címzett(ek) képes(ek) lesz(nek) felfedni ezt az üzenetet. - Elhagy + Elhagyás Rendben Nincsenek szűrt csevegések érvénytelen adat @@ -813,9 +813,9 @@ Csak a csoporttulajdonosok módosíthatják a csoportbeállításokat. Nincsenek előzmények Érvénytelen QR-kód - Megjelölés olvasottként + Olvasottnak jelölés ÉLŐ - Olvasatlannak jelölve + Olvasatlannak jelölés Több Bejelentkezés hitelesítő adatokkal érvénytelen üzenet formátum @@ -847,7 +847,7 @@ Beszélgessünk a SimpleX Chat-ben Moderálva lett ekkor: Élő üzenetek - Ellenőrzöttként jelölve + Hitelesítés Üzenetkézbesítési bizonylatok! hivatkozás előnézeti képe Csoport elhagyása? @@ -1008,7 +1008,7 @@ Küldés %s másodperc %s: %s - A SimpleX nem tud futni a háttérben. Csak akkor fog értesítéseket kapni, ha az alkalmazás fut. + A SimpleX nem tud a háttérben futni. Csak akkor fog értesítéseket kapni, amikor az alkalmazás meg van nyitva. Túl sok kép! Archívum mentése %s, %s és %d tag @@ -1026,7 +1026,7 @@ SimpleX csoport hivatkozás Képre várakozás Önmegsemmisítés - várakozás válaszra… + várakozás a válaszra… Ismerős nevének beállítása… Tag feloldása QR-kód beolvasása @@ -1040,21 +1040,21 @@ Biztonsági kód Adja meg a helyes aktuális jelmondatát. Az elküldött üzenetek végleges törlése le van tiltva. - Üzenetreakció tiltása. + Az üzenetreakciók küldése le van tiltva. Véletlenszerű jelmondat használata egyenrangú CSEVEGÉSI SZOLGÁLTATÁS INDÍTÁSA Fogadott hivatkozás beillesztése Kiszolgálók mentése? A SimpleX Chat biztonsága a Trail of Bits által lett auditálva. - módosított csoport profil + frissítette a csoport profilját TÁMOGASSA A SIMPLEX CHATET SimpleX Chat szolgáltatás Nem lehet üzeneteket küldeni! %s ellenőrzött Jelszó megjelenítése Adatvédelem és biztonság - Tag eltávolítása + Eltávolítás A jelkód beállítva! Elküldött üzenet Ismerősök kiválasztása @@ -1078,7 +1078,7 @@ Ennek az eszköznek a neve Jelenlegi profil Fájl feltöltése - Hang- és videóhívások tiltása. + A hívások kezdeményezése le van tiltva. Megkövetelt SimpleX Chat üzenetek Visszaállítás @@ -1097,7 +1097,7 @@ SimpleX egyszer használatos meghívó Hívások nem sikerült elküldeni - TÉMA SZÍNEK + KEZELŐFELÜLET SZÍNEI Visszaállít Előző jelszó megadása az adatbázis biztonsági mentésének visszaállítása után. Ez a művelet nem vonható vissza. Másodlagos @@ -1117,7 +1117,7 @@ Ismeretlen hiba Saját kiszolgáló cím Csevegés konzol megnyitása - Tag eltávolítása + Eltávolítás Adatbázis jelmondat beállítása Biztonsági kód megtekintése Tag feloldása? @@ -1143,7 +1143,7 @@ Csatlakozási kérés megismétlése? Képre várakozás Hangüzenetek - Tag eltávolítása? + Biztosan eltávolítja? Biztonsági kód ellenőrzése eltávolítottak SimpleX azonosító @@ -1158,12 +1158,12 @@ Változáslista Csoport megnyitása Elküldve ekkor: - Hangüzenetek küldése le van tiltva. + A hangüzenetek küldése le van tiltva. Utolsó üzenetek megjelenítése Az előre beállított kiszolgáló címe Rendszeres értesítések letiltva! A jelkód megváltozott! - Akkor fut, ha az alkalmazás nyitva van + Akkor fut, amikor az alkalmazás meg van nyitva Ez a QR-kód nem egy hivatkozás! Fájlra várakozás simplexmq: v%s (%2s) @@ -1193,10 +1193,9 @@ Mentés Váltás Kapott hivatkozás beillesztése az ismerősökhöz történő kapcsolódáshoz… - Kód beolvasása + Beolvasás Port megnyitása a tűzfalon indítás… - Szín mentése Leállítás elküldve SOCKS proxy használata @@ -1206,7 +1205,7 @@ Alkalmazás képernyőjének védelme QR-kód megjelenítése videóhívás - Nem kedvenc + Kedvenc törlése Üzenet kézbesítési jelentések küldése SimpleX azonosító Koppintson a @@ -1230,7 +1229,7 @@ Jelmondat mentése és csevegés megnyitása Beállítások mentése? Az első csevegési rendszer bármiféle felhasználó azonosító nélkül - privátra lett tervezre. - A közvetlen üzenetek küldése a tagok számára le van tiltva. + A közvetlen üzenetek küldése le van tiltva a tagok között. SOCKS proxy használata? Hangszóró kikapcsolva hét @@ -1278,7 +1277,7 @@ Sikertelen kiszolgáló-teszt! Kapcsolat ellenőrzése Tudjon meg többet - A küldő megszakította a fájl átvitelt. + A fájl küldője visszavonta az átvitelt. Csevegési szolgáltatás megállítása? Fogadva ekkor: Beállítva 1 nap @@ -1316,8 +1315,8 @@ Számítógép azonosítójának beillesztése kapcsolattartási azonosító-hivatkozáson keresztül SimpleX háttérszolgáltatást használja - az akkumulátor néhány százalékát használja naponta.]]> - 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) + Az ismerősének online kell lennie ahhoz, hogy a kapcsolat létrejöjjön. +\nVisszavonhatja ezt a kapcsolatfelvételt és törölheti az ismerőst (ezt később ismét megpróbálhatja egy új hivatkozással). A jelszó nem található a Keystore-ban, ezért kézzel szükséges megadni. Ez akkor történhetett meg, ha visszaállította az alkalmazás adatait egy biztonsági mentési eszközzel. Ha nem így történt, akkor lépjen kapcsolatba a fejlesztőkkel. Az ismerősei továbbra is kapcsolódva maradnak. A kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát @@ -1361,7 +1360,7 @@ %1$s.]]> Profil felfedése Ez a hivatkozás nem érvényes kapcsolati hivatkozás! - 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. + A végpontok közötti titkosítás ellenőrzéséhez hasonlítsa össze (vagy olvassa be a QR-kódot) az ismerőse eszközén lévő kóddal. A csevegési adatbázis legfrissebb verzióját CSAK egy eszközön kell használnia, ellenkező esetben előfordulhat, hogy az üzeneteket nem fogja megkapni valamennyi ismerősétől. Ez a beállítás a jelenlegi csevegési profilban lévő üzenetekre érvényes Meghívást kapott a csoportba. Csatlakozzon, hogy kapcsolatba léphessen a csoport tagjaival. @@ -1377,7 +1376,7 @@ Egy olyan ismerősét próbálja meghívni, akivel inkognitóprofilt osztott meg abban a csoportban, amelyben a saját fő profilja van használatban %1$s csoporthoz.]]> Amikor az alkalmazás fut - 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 + Inkognító profilt használ ehhez a csoporthoz - fő profilja megosztásának elkerülése érdekében a meghívók küldése le van tiltva Kapcsolat izolációs mód Akkor lesz kapcsolódva, ha a kapcsolódási kérelme elfogadásra kerül, várjon, vagy ellenőrizze később! A hangüzenetek küldése le van tiltva ebben a csoportban. @@ -1437,11 +1436,11 @@ Megoszthat egy hivatkozást vagy QR-kódot - így bárki csatlakozhat a csoporthoz. Ha a csoport később törlésre kerül, akkor nem fogja elveszíteni annak tagjait. Csatlakozott ehhez a csoporthoz %1$s csoporthoz!]]> - A hangüzenetek le vannak tiltva ebben a csevegésben. + A hangüzenetek küldése le van tiltva ebben a csevegésben. Ön irányítja csevegését! Kód ellenőrzése a számítógépen Az időzóna védelme érdekében a kép-/hangfájlok UTC-t használnak. - Csoporttag részére a kapcsolódási kérelem elküldésre kerül. + A kapcsolódási kérelem elküldésre kerül ezen csoporttag számára Inkognitóprofil megosztása esetén a rendszer azt a profilt fogja használni azokhoz a csoportokhoz, amelyekbe meghívást kapott. Már kért egy kapcsolódási kérelmet ezen az azonosítón keresztül! Megoszthatja ezt a SimpleX azonosítót az ismerőseivel, hogy kapcsolatba léphessenek vele: %s. @@ -1489,13 +1488,13 @@ Kézbesítési jelentések engedélyezve vannak a(z) %d csoportban A szerepkör meg fog változni erre: \"%s\". A csoportban mindenki értesítve lesz. Profil és kiszolgálókapcsolatok - Üzenetküldő és alkalmazásplatform, amely védi az ön adatvédelmét és biztonságát. + Egy üzenetküldő- és alkalmazásplatform, amely védi az ön adatait és biztonságát. A profil aktiválásához koppintson az ikonra. Kézbesítési jelentések le vannak tiltva %d ismerősnél Munkamenet kód Köszönet a felhasználóknak - hozzájárulás a Weblaten! Kis csoportok (max. 20 tag) - Az ön által elfogadott kapcsolat megszakad! + Az ön által elfogadott kapcsolat vissza lesz vonva! Élő üzenet küldése - a címzett(ek) számára frissül, ahogy beírja A KÉZBESÍTÉSI JELENTÉSEKET A KÖVETKEZŐ CÍMRE KELL KÜLDENI A következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel). @@ -1598,7 +1597,7 @@ Privát jegyzetek Hiba a privát jegyzetek törlésekor Hiba az üzenet létrehozásakor - Privát jegyzetek törlése? + Privát jegyzetek kiürítése? Létrehozva ekkor: Mentett üzenet Megosztva ekkor: %s @@ -1619,7 +1618,7 @@ Letiltva az admin által %s letiltva Mindenki számára letiltva - Tag letiltása mindenki számára? + Mindenki számára letiltja ezt a tagot? %d üzenet letiltva az admin által Letiltás feloldása mindenki számára Mindenki számára feloldja a tag letiltását? @@ -1680,7 +1679,7 @@ Csevegés indítása Nem szabad ugyanazt az adatbázist használni egyszerre két eszközön.]]> Erősítse meg, hogy emlékszik az adatbázis jelmondatára az átköltöztetéshez. - Átköltöztetés egy másik eszközről opciót az új eszközön és szkennelje be a QR-kódot.]]> + Átköltöztetés egy másik eszközről opciót az új eszközön és olvassa be a QR-kódot.]]> Átköltöztetés véglegesítése Átköltöztetés véglegesítése egy másik eszközön. Letöltés előkészítése @@ -1708,8 +1707,8 @@ Ez a csevegés végpontok közötti titkosítással védett. Átköltöztetési párbeszédablak megnyitása Ez a csevegés végpontok közötti kvantumrezisztens tikosítással védett. - végpontok közötti titkosítással é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.]]> - végpontok közötti kvantumrezisztens titkosítással é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.]]> + végpontok közötti titkosítással, sérülés utáni titkosság-védelemmel és -helyreállítással, továbbá visszautasítással védi.]]> + végpontok közötti kvantumrezisztens titkosítással, sérülés utáni titkosság-védelemmel és -helyreállítással, továbbá visszautasítással védi.]]> Hiba az értesítés megjelenítésekor, lépjen kapcsolatba a fejlesztőkkel. Keresse meg ezt az engedélyt az Android beállításaiban, és adja meg kézzel. Engedélyezés a beállításokban @@ -1733,7 +1732,7 @@ minden tag SimpleX hivatkozás A hangüzenetek küldése le van tiltva - A SimpleX hivatkozások küldése ebben a csoportban le van tiltva. + A SimpleX hivatkozások küldése le van tiltva ebben a csoportban. A SimpleX hivatkozások küldése le van tiltva Fájlok és média tartalom küldése le van tiltva A SimpleX hivatkozások küldése engedélyezve van. @@ -1764,4 +1763,93 @@ Profilképek Profilkép alakzat Négyzet, kör vagy bármi a kettő között. + Célkiszolgáló hiba: %1$s + Továbbító kiszolgáló: %1$s +\nHiba: %2$s + Hálózati problémák - az üzenet többszöri elküldési kísérlet után lejárt. + A kiszolgáló verziója nem kompatibilis a hálózati beállításokkal. + Rossz kulcs vagy ismeretlen kapcsolat - valószínűleg ez a kapcsolat törlődött. + Továbbító kiszolgáló: %1$s +\nCélkiszolgáló hiba: %2$s + Hiba: %1$s + Kapacitás túllépés - a címzett nem kapta meg a korábban elküldött üzeneteket. + Üzenet kézbesítési figyelmeztetés + A kiszolgáló címe nem kompatibilis a hálózati beállításokkal. + Soha + Ismeretlen átjátszók + Ha az IP-cím rejtett + Üzenet állapot megjelenítése + Korábbi verzióra történő visszatérés engedélyezése + Mindig + Nem + Nem védett + Igen + Ne használjon privát útválasztást. + Privát útválasztás + Használjon privát útválasztást ismeretlen kiszolgálókkal. + Mindig használjon privát útválasztást. + Üzenet útválasztási mód + Közvetlen üzenetküldés, ha az IP-cím védett és az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + Közvetlen üzenetküldés, ha az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + Az IP-címe védelme érdekében a privát útválasztás az SMP-kiszolgálókat használja az üzenetek kézbesítéséhez. + Üzenet útválasztási tartalék + PRIVÁT ÜZENET ÚTVÁLASZTÁS + Privát útválasztás használata ismeretlen kiszolgálókkal, ha az IP-cím nem védett. + Ne küldjön üzeneteket közvetlenül, még akkor sem, ha az ön kiszolgálója vagy a célkiszolgáló nem támogatja a privát útválasztást. + Tor vagy VPN nélkül az IP-címe látható lesz a fájlkiszolgálók számára. + FÁJLOK + Az IP-cím védelme + Az alkalmazás kérni fogja az ismeretlen fájlkiszolgálókról történő letöltések megerősítését (kivéve, ha az .onion vagy a SOCKS proxy engedélyezve van). + Ismeretlen kiszolgálók! + Tor vagy VPN nélkül az IP-címe látható lesz az XFTP átjátszók számára: +\n%1$s. + Minden színmód + Fekete + Színmód + Sötét + Sötét mód + Sötét mód színei + Illesztés + Jó napot! + Jó reggelt! + Haladó beállítások + Alkalmazás erre + Csevegés színei + Csevegés témája + Kitöltés + Profiltéma + Csevegőlista megjelenítése új ablakban + Világos + Világos mód + Kapott válasz + Kép eltávolítása + Mozaik + Szín visszaállítása + Méretezés + Elküldött válasz + Alapértelmezett téma beállítása + Rendszer + Háttérkép kiemelés + Háttérkép háttérszíne + További kiemelés 2 + Alkalmazás téma + Perzsa kezelőfelület + Védje IP-címét az ismerősei által kiválasztott üzenetküldő átjátszókkal szemben. +\nEngedélyezze a beállításokban a *Hálózat és kiszolgálók* menüpontban. + Ismeretlen kiszolgálókról származó fájlok jóváhagyása. + Javított üzenetkézbesítés + Alkalmazás témájának visszaállítása + Tegye egyedivé a csevegéseit! + Új csevegési témák + Privát üzenet útválasztás 🚀 + Fájlok biztonságos fogadása + Csökkentett akkumulátor-használattal. + Hiba a WebView inicializálásában. Frissítse rendszerét az új verzióra. Kérjük, lépjen kapcsolatba a fejlesztőkkel. +\nHiba: %s + Felhasználó által létrehozott téma visszaállítása + Üzenet várakoztatási információ + nincs + Hibakeresés kézbesítés + Kiszolgáló várakoztatási infó: %1$s +\nUtoljára kézbesített üzenet: %2$s \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_palette.svg b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_palette.svg new file mode 100644 index 0000000000..f36ef054e0 --- /dev/null +++ b/apps/multiplatform/common/src/commonMain/resources/MR/images/ic_palette.svg @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_cats@4x.png b/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_cats@4x.png new file mode 100644 index 0000000000..9bff3eb3d0 Binary files /dev/null and b/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_cats@4x.png differ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_flowers@4x.png b/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_flowers@4x.png new file mode 100644 index 0000000000..e0ee4b057d Binary files /dev/null and b/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_flowers@4x.png differ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_hearts@4x.png b/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_hearts@4x.png new file mode 100644 index 0000000000..35da7c7aed Binary files /dev/null and b/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_hearts@4x.png differ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_kids@4x.png b/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_kids@4x.png new file mode 100644 index 0000000000..f5f15d3643 Binary files /dev/null and b/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_kids@4x.png differ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_school@4x.png b/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_school@4x.png new file mode 100644 index 0000000000..f6e1cce383 Binary files /dev/null and b/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_school@4x.png differ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_travel@4x.png b/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_travel@4x.png new file mode 100644 index 0000000000..64ec137331 Binary files /dev/null and b/apps/multiplatform/common/src/commonMain/resources/MR/images/wallpaper_travel@4x.png differ 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 310a021ff2..2ea3c53514 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/it/strings.xml @@ -847,7 +847,6 @@ Proibisci l\'invio di messaggi vocali. ricevuto, vietato Ripristina i colori - Salva colore Imposta 1 giorno Imposta le preferenze del gruppo Tema @@ -1120,7 +1119,7 @@ Per connettervi, il tuo contatto può scansionare il codice QR o usare il link nell\'app. Quando le persone chiedono di connettersi, puoi accettare o rifiutare. Indirizzo SimpleX - COLORI DEL TEMA + COLORI DELL\'INTERFACCIA I tuoi contatti resteranno connessi. Aggiungi l\'indirizzo al tuo profilo, in modo che i tuoi contatti possano condividerlo con altre persone. L\'aggiornamento del profilo verrà inviato ai tuoi contatti. Crea un indirizzo per consentire alle persone di connettersi con te. @@ -1148,7 +1147,7 @@ Secondario Messaggio ricevuto Messaggio inviato - Titolo + Titoli Link una tantum Info sull\'indirizzo SimpleX Tutti i tuoi contatti resteranno connessi. L\'aggiornamento del profilo verrà inviato ai tuoi contatti. @@ -1769,4 +1768,94 @@ La fonte del messaggio resta privata. Forma delle immagini del profilo Quadrata, circolare o qualsiasi forma tra le due + Server di inoltro: %1$s +\nErrore del server di destinazione: %2$s + Server di inoltro: %1$s +\nErrore: %2$s + Problemi di rete - messaggio scaduto dopo molti tentativi di inviarlo. + L\'indirizzo del server non è compatibile con le impostazioni di rete. + La versione del server non è compatibile con le impostazioni di rete. + Chiave sbagliata o connessione sconosciuta - molto probabilmente questa connessione è stata eliminata. + Errore del server di destinazione: %1$s + Errore: %1$s + Quota superata - il destinatario non ha ricevuto i messaggi precedentemente inviati. + Avviso di consegna del messaggio + Instradamento privato + Mai + Relay sconosciuti + Usa l\'instradamento privato con server sconosciuti. + Modalità instradamento messaggio + Usa l\'instradamento privato con server sconosciuti quando l\'indirizzo IP non è protetto. + + Invia messaggi direttamente quando il tuo server o quello di destinazione non supporta l\'instradamento privato. + Quando l\'IP è nascosto + Ripiego instradamento messaggio + Mostra stato del messaggio + Consenti downgrade + Sempre + Usa sempre l\'instradamento privato. + NON inviare messaggi direttamente, anche se il tuo server o quello di destinazione non supporta l\'instradamento privato. + NON usare l\'instradamento privato. + No + INSTRADAMENTO PRIVATO MESSAGGI + Invia messaggi direttamente quando l\'indirizzo IP è protetto e il tuo server o quello di destinazione non supporta l\'instradamento privato. + Per proteggere il tuo indirizzo IP, l\'instradamento privato usa i tuoi server SMP per consegnare i messaggi. + Non protetto + Server sconosciuti! + Proteggi l\'indirizzo IP + L\'app chiederà di confermare i download da server di file sconosciuti (eccetto .onion o quando il proxy SOCKS è attivo). + Senza Tor o VPN, il tuo indirizzo IP sarà visibile ai server di file. + FILE + Senza Tor o VPN, il tuo indirizzo IP sarà visibile a questi relay XFTP: +\n%1$s. + Tema della chat + Nero + Modalità di colore + Scura + Modalità scura + Principale aggiuntivo 2 + Tutte le modalità di colore + Applica a + Colori modalità scura + Tema del profilo + Risposta ricevuta + Colori della chat + Riempi + Chiara + Modalità chiara + Ripristina colore + Impostazioni avanzate + Rimuovi immagine + Ripeti + Scala + Adatta + Risposta inviata + Imposta tema predefinito + Mostra la lista di chat in una nuova finestra + Sistema + Tinta dello sfondo + Buon pomeriggio! + Buongiorno! + Retro dello sfondo + Instradamento privato dei messaggi 🚀 + Proteggi il tuo indirizzo IP dai relay di messaggistica scelti dai tuoi contatti. +\nAttivalo nelle impostazioni *Rete e server*. + Errore di inizializzazione di WebView. Aggiorna il sistema ad una nuova versione. Contatta gli sviluppatori. +\nErrore: %s + Tema dell\'app + Ripristina al tema dell\'app + Ripristina al tema dell\'utente + Conferma i file da server sconosciuti. + Consegna dei messaggi migliorata + Cambia l\'aspetto delle tue chat! + Nuovi temi delle chat + Interfaccia in persiano + Ricevi i file in sicurezza + Con consumo di batteria ridotto. + Info coda messaggi + nessuna + info coda server: %1$s +\n +\nultimo msg ricevuto: %2$s + Debug della consegna \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml index 7899c374b2..e335c199d0 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/iw/strings.xml @@ -898,7 +898,6 @@ שמור סיסמה ופתח את הצ׳אט שמור ארכיון בחירת אנשי קשר - שמור צבע קוד גישה להשמדה עצמית שניות אישור diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml index 83d59b3705..41f1d0d6a8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ja/strings.xml @@ -792,7 +792,6 @@ 保存 ネットワーク設定を更新しますか? 設定を更新すると、全サーバにクライントの再接続が行われます。 - 色を保存 あなたが次を許可しています: オン グループ設定を行う @@ -1769,4 +1768,32 @@ プロフィール画像 正方形、円形またはその中間 プロフィール画像をシェイプ + 宛先サーバエラー: %1$s + エラー: %1$s + プライベートルーティング + 不明なリレー + 未保護 + ダウングレードを許可 + 常時 + 常時プライベートルーティングを使用 + プライベートメッセージルーティング + メッセージステータスを表示 + システム + ブラック + 色設定 + ダーク + ダークモードカラー + ライト + アプリのテーマ + ダークモード + ライトモード + 適用先 + 追加のアクセント2 + 高度な設定 + こんにちは! + おはよう! + 壁紙のアクセント + 壁紙の背景 + チャットカラー + チャットテーマ \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml index af3972035f..3caa99bc59 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ko/strings.xml @@ -757,7 +757,6 @@ 저장하고 대화 상대에게 알리기 지우기 아카이브 저장하기 - 색상 저장하기 암호 저장소에 비밀번호 저장하기 데이터베이스 백업 복원하기 데이터베이스 백업을 복원한 후 이전 비밀번호를 입력해 주세요. 이 작업은 되돌릴 수 없어요. diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml index 43237fa2a9..b5191f342d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/lt/strings.xml @@ -189,7 +189,6 @@ Daugiau neberodyti Tamsus Atstatyti spalvas - Įrašyti spalvą Grupės parinktys Ištrinti visiems Tiesioginės žinutės diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml index 92bd3e381a..455f9d53bd 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ml/strings.xml @@ -323,7 +323,6 @@ പഴയപടിയാക്കുക സംവിധാനം സംവിധാനം - നിറം സംരക്ഷിക്കുക ശീർഷകം രണ്ടാംതരമായ അതെ 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 ab10a01a66..4fa3ba4f51 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/nl/strings.xml @@ -814,7 +814,6 @@ Wanneer je een incognito profiel met iemand deelt, wordt dit profiel gebruikt voor de groepen waarvoor ze je uitnodigen. Thema Kleuren resetten - Kleur opslaan Systeem ja gekregen, verboden @@ -1165,7 +1164,7 @@ Zorg ervoor dat het bestand de juiste YAML-syntaxis heeft. Exporteer het thema om een voorbeeld te hebben van de themabestandsstructuur. Database openen… Gebruikershandleiding.]]> - THEMA KLEUREN + INTERFACE KLEUREN U kunt uw adres delen als een link of QR-code - iedereen kan verbinding met u maken. Alle app-gegevens worden verwijderd. Er wordt een leeg chatprofiel met de opgegeven naam gemaakt en de app wordt zoals gewoonlijk geopend. @@ -1767,4 +1766,94 @@ Vorm profiel afbeeldingen Profiel afbeeldingen Vierkant, cirkel of iets daartussenin. + Capaciteit overschreden - ontvanger heeft eerder verzonden berichten niet ontvangen. + Fout met bestemmingsserver: %1$s + Fout: %1$s + Doorstuurserver: %1$s +\nBestemmingsserverfout: %2$s + Doorstuurserver: %1$s +\nFout: %2$s + Waarschuwing voor berichtbezorging + Netwerkproblemen - bericht is verlopen na vele pogingen om het te verzenden. + Serveradres is niet compatibel met netwerkinstellingen. + Serverversie is incompatibel met netwerkinstellingen. + Verkeerde sleutel of onbekende verbinding - hoogstwaarschijnlijk is deze verbinding verwijderd. + Altijd + Privéroutering + Onbekende relays + Nooit + Onbeschermd + Gebruik altijd privéroutering. + Gebruik privéroutering met onbekende servers. + Gebruik GEEN privéroutering. + Berichtrouteringsmodus + Gebruik privéroutering met onbekende servers wanneer het IP-adres niet beveiligd is. + Downgraden toestaan + Wanneer IP verborgen is + Ja + Nee + Stuur berichten rechtstreeks wanneer uw of de doelserver geen privéroutering ondersteunt. + Toon berichtstatus + Om uw IP-adres te beschermen, gebruikt privéroutering uw SMP-servers om berichten te bezorgen. + Stuur GEEN berichten rechtstreeks, zelfs als uw of de bestemmingsserver geen privéroutering ondersteunt. + Terugval op berichtroutering + PRIVÉBERICHT ROUTING + Stuur berichten rechtstreeks als het IP-adres beschermd is en uw of bestemmingsserver geen privéroutering ondersteunt. + Onbekende servers! + Zonder Tor of VPN is uw IP-adres zichtbaar voor deze XFTP-relays: +\n%1$s. + Zonder Tor of VPN is uw IP-adres zichtbaar voor bestandsservers. + BESTANDEN + Bescherm het IP-adres + De app vraagt om downloads van onbekende bestandsservers te bevestigen (behalve .onion of wanneer SOCKS-proxy is ingeschakeld). + Fout bij het initialiseren van WebView. Update uw systeem naar de nieuwe versie. Neem contact op met ontwikkelaars. +\nFout: %s + Achtergrond accent + Vullen + Passen + Goedemiddag! + Goedemorgen! + Verwijder afbeelding + Schaal + Alle kleurmodi + Toepassen op + Lichte modus + Laat uw chats er anders uitzien! + Nieuwe chatthema\'s + Routing van privéberichten🚀 + Bevestig bestanden van onbekende servers. + Verbeterde bezorging van berichten + Perzische gebruikersinterface + Veilig bestanden ontvangen + Met verminderd batterijgebruik. + App thema + Terugzetten naar app thema + Terugzetten naar gebruikersthema + Donkere modus + Geavanceerde instellingen + Bescherm uw IP-adres tegen de berichtenrelais die door uw contacten zijn gekozen. +\nSchakel dit in in *Netwerk en servers*-instellingen. + Herhalen + Chatkleuren + Profiel thema + Chat thema + Extra accent 2 + Zwart + Kleur mode + Donker + Kleuren in donkere modus + Licht + Antwoord ontvangen + Kleur opnieuw instellen + Antwoord verzonden + Systeem + Wallpaper achtergrond + Stel het standaard thema in + Toon chatlijst in nieuw venster + geen + Foutopsporing bezorging + Informatie over berichtenwachtrij + informatie over serverwachtrij: %1$s +\n +\nlaatst ontvangen bericht: %2$s \ 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 d667126730..4853fdf127 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pl/strings.xml @@ -799,7 +799,6 @@ Zabroń wysyłania znikających wiadomości. otrzymane, zabronione Resetuj kolory - Zapisz kolor Ustaw 1 dzień System System @@ -1154,7 +1153,7 @@ Kiedy ludzie proszą o połączenie, możesz je zaakceptować lub odrzucić. Nie stracisz kontaktów, jeśli później usuniesz swój adres. Dostosuj motyw - KOLORY MOTYWU + KOLORY INTERFEJSU Twoje kontakty pozostaną połączone. Dodaj adres do swojego profilu, aby Twoje kontakty mogły go udostępnić innym osobom. Aktualizacja profilu zostanie wysłana do Twoich kontaktów. Utwórz adres, aby ludzie mogli się z Tobą połączyć. @@ -1769,4 +1768,94 @@ Kwadrat, okrąg lub cokolwiek pomiędzy. Źródło wiadomości pozostaje prywatne. Zostanie włączone w czatach bezpośrednich! + Zawsze używaj prywatnego trasowania. + Zezwól na obniżenie wersji + Zawsze + Przekroczono pojemność - odbiorca nie otrzymał wcześniej wysłanych wiadomości. + Błąd serwera docelowego: %1$s + Błąd: %1$s + Serwer przekazujący: %1$s +\nBłąd serwera docelowego: %2$s + Serwer przekazujący: %1$s +\nBłąd: %2$s + Ostrzeżenie dostarczenia wiadomości + Błąd sieciowy - wiadomość wygasła po wielu próbach wysłania jej. + Adres serwera jest niekompatybilny z ustawieniami sieciowymi. + Wersja serwera jest niekompatybilna z ustawieniami sieciowymi. + Zły klucz lub nieznane połączenie - najprawdopodobniej to połączenie jest usunięte. + Nigdy + Niezabezpieczony + NIE używaj prywatnego trasowania. + Tryb trasowania wiadomości + Tak + Nie + Gdy IP ukryty + Pokaż status wiadomości + TRASOWANIE PRYWATNYCH WIADOMOŚCI + NIE wysyłaj wiadomości bezpośrednio, nawet jeśli serwer docelowy nie obsługuje prywatnego trasowania. + Aby chronić Twój adres IP, prywatne trasowanie używa Twoich serwerów SMP, aby dostarczyć wiadomości. + Nieznane przekaźniki + Używaj prywatnego trasowania z nieznanymi serwerami. + Rezerwowe trasowania wiadomości + Prywatne trasowanie + Wysyłaj wiadomości bezpośrednio, gdy adres IP jest chroniony i Twój lub docelowy serwer nie obsługuje prywatnego trasowania. + Wysyłaj wiadomości bezpośrednio, gdy Twój lub docelowy serwer nie obsługuje prywatnego trasowania. + Używaj prywatnego trasowania z nieznanymi serwerami, gdy adres IP nie jest chroniony. + Nieznane serwery! + Bez Tor lub VPN, Twój adres IP będzie widoczny dla tych przekaźników XFTP: +\n%1$s. + Chroń adres IP + Aplikacja będzie prosić o potwierdzenie pobierań z nieznanych serwerów plików (z wyjątkiem .onion lub gdy proxy SOCKS jest włączone). + Bez Tor lub VPN, Twój adres IP będzie widoczny do serwerów plików. + PLIKI + Motyw profilu + Pokaż listę czatów w nowym oknie + Kolory ciemnego trybu + Jasny + Otrzymano odpowiedź + Usuń obraz + Zresetuj kolory + Wypełnij + Dopasuj + Dzień dobry! + Dzień dobry! + Jasny tryb + Powtórz + Dodatkowy akcent 2 + Wszystkie tryby kolorów + Ciemny tryb + Ustaw domyślny motyw + Systemowy + Tło tapety + Zaawansowane ustawienia + Zastosuj dla + Skaluj + Czarny + Akcent tapety + Wyślij odpowiedź + Kolory czatu + Motyw czatu + Tryb koloru + Ciemny + Błąd inicjacji WebView. Zaktualizuj swój system do nowej wersji. Proszę skontaktować się z deweloperami. +\nBłąd: %s + nic + Nowy motywy czatu + Trasowanie prywatnych wiadomości🚀 + Bezpiecznie otrzymuj pliki + Ulepszona dostawa wiadomości + Perski interfejs użytkownika + Potwierdzaj pliki z nieznanych serwerów. + Dostarczenie debugowania + Zrób wygląd Twoich czatów inny! + Chroni Twój adres IP przed przekaźnikami wiadomości wybranych przez Twoje kontakty. +\nWłącz w ustawianiach *Sieć i serwery* . + Informacje kolejki wiadomości + Informacje kolejki serwera: %1$s +\n +\nostatnia otrzymana wiadomość: %2$s + Ze zredukowanym zużyciem baterii. + Motyw aplikacji + Zresetuj do motywu aplikacji + Zresetuj do motywu użytkownika \ 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 cc0bd49454..7d4570e561 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 @@ -485,7 +485,6 @@ Reverter Salvar Redefinir cores - Salvar cor interface italiana Notificações periódicas Câmera diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml index 7889ef396e..4bb442faf8 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/pt/strings.xml @@ -370,7 +370,6 @@ Você está a tentar convidar um contato com quem partilhou um perfil anónimo para o grupo no qual voçê está a usar o seu perfil principal Conexão O modo anónimo protege a privacidade do nome e da imagem do seu perfil principal — para cada novo contato um novo perfil aleatório é criado. - Salvar cor você partilhou ligação de utilização única você partilhou ligação anónima de utilização única Ligação de conexão inválida diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml index b6f268e6f1..ae03ebd1ae 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ro/strings.xml @@ -155,4 +155,169 @@ Arabă, Bulgară, Finlandeză, Ebraică, Thailandeză și Ucraineană - mulțumită utilizatorilor și Weblate. Apelurile audio/video sunt interzise. (prezent) + Poză de profil eliminată + Temă întunecată + eliminat + Întunecată + Adresă de contact eliminată + Personalizează și distribuie teme colorate. + Teme personalizate + Repetă descărcarea + Timp personalizat + Elimină membru + Repetă importarea + Personalizează tema + Elimină + Elimină + Elimină membru + Elimini membrul? + Resetează la implicit + Resetează culoarea + Resetează culorile + Elimină imagine + Repetă încărcarea + apel respins + Elimini fraza de acces din Keystore? + Elimini fraza de acces din setări? + Repetă cererea de conectare? + Necesar + Reîncearcă + Primește fișiere în siguranță + Grupuri mai sigure + Restabilește + Reîmprospătează + Revoci fișierul? + Revocă + Renegociezi criptarea? + Resetează + Respinge + Salvează fraza de acces în setări + Salvează și actualizează profilul grupului + Revenire + Repetă cererea de alăturare? + Repornește conversația + salvat + Salvat de la %s + Salvează + Salvat + Salvat de la + Renegociază + Salvează servere + Servere WebRTC ICE salvate vor fi eliminate. + Salvează parola profilului + Salvează fraza de acces și deschide conversația + %s și %s + Renegociază criptarea + Salvează și notifică contactul + Salvează și notifică contactele + Salvează și notifică membrii grupului + Rulează când aplicația este pornită + Răspunde + Revocă fișierul + Salvează + Salvezi setările? + Salvezi preferințe? + Repornire + Restabilește copia de rezervă a bazei de date + Restabilești copia de rezervă a bazei de date? + Eroare la restabilirea bazei de date + %1$s eliminat + %s și %s conectați + Rol + Salvează + Arată + Respinge + Salvezi servere? + Apel respins + Mesaj salvat + Salvează arhiva + Repornește aplicația pentru a crea un nou profil + Salvează fraza de acces în Keystore + Salvează profilul grupului + Repetă + Trimite previzualizări ale link-ului + Setează frază de acces + Distribuie adresă + Trimis la + Secundar + Mesaj trimis + Setează preferințele grupului + trimis + Adresa serverului este incompatibilă cu setările de rețea. + Versiunea serverului este incompatibilă cu setările de rețea. + Trimițând prin + trimiterea de fișiere nu este acceptată încă + Scanează codul de securitate din aplicația contactului tău + Selectează contacte + Autodistrugere + Răspuns trimis + Distribuie media… + Distribuie mesaj… + Arată lista conversațiilor într-o fereastră nouă + Arată consola într-o fereastră nouă + Setează fraza de acces a bazei de date + Setează fraza de acces a bazei de date + setează adresă de contact nouă + %s (actual) + Cod de sesiune + Expeditorul a anulat transferul de fișiere. + Serverul necesită autorizație pentru a crea cozi, verifică parola + Distribuie + trimitere eșuată + Caută sau lipește link SimpleX + Setează numele de contact + Salvezi mesajul de bun venit? + Evaluare de securitate + Scanează cod QR de pe desktop + Caută + Trimite un mesaj live - se va actualiza pentru destinatar(i) în timp ce îl tastezi + Distribuie fișier + Trimite până la ultimele 100 de mesaje membrilor noi. + Bara de căutare acceptă link-uri de invitație. + secunde + Scanează de pe mobil + Mesaj trimis + Scanează cod QR + Trimite + Trimite întrebări și idei + Arată opțiuni dezvoltator + sec + Mesajele trimise vor fi șterse după timpul setat. + Serverul necesită autorizație pentru a încărca, verifică parola + Arată contact și mesaje + Distribuie fișier… + Setează numele de contact… + Trimite mesaj + Trimite mesaj temporar + (scanează sau lipește din clipboard) + Arată cod QR + Test server eșuat! + Arată: + Arată erori interne + secret + SETĂRI + %s conectat + setează imagine de profil + Trimis către: %s + SERVERE + Trimite mesaj live + %s descărcat + Distribui adresa cu contactele? + Arată previzualizare + trimite mesaj direct + Trimite mesaj direct pentru a te conecta + Selectează + Trimiterea de fișiere va fi oprită. + Trimite + Setări + Scanează cod + Cod de securitate + Trimite-ne email + Scanează codul QR al serverului + Distribuie contactelor + Arată + cod de securitate schimbat + Arată ultimul mesaj + Trimite mesaj direct + Setează tema implicită \ 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 a71638eae0..88dd4c2783 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/ru/strings.xml @@ -848,7 +848,6 @@ Темная Тема - Сохранить цвет Сбросить цвета Акцент @@ -1250,7 +1249,7 @@ Запретить реакции на сообщения. Запретить реакции на сообщения. секунд - ЦВЕТА ТЕМЫ + ЦВЕТА ИНТЕРФЕЙСА Поделиться адресом с контактами\? Обновлённый профиль будет отправлен Вашим контактам. Об адресе SimpleX @@ -1851,4 +1850,95 @@ Форма картинок профилей Квадрат, круг и все, что между ними. Будет включено в прямых разговорах! + ФАЙЛЫ + Новые темы чатов + нет + Светлая + Системная + Цвета тёмного режима + Получайте файлы безопасно + Конфиденциальная доставка сообщений 🚀 + Улучшенная доставка сообщений + Уменьшенный расход батареи. + Версия сервера несовместима с настройками сети. + Неверный ключ или неизвестное соединение - скорее всего, это соединение удалено. + Превышено количество сообщений - предыдущие сообщения не доставлены. + Ошибка сервера получателя: %1$s + Ошибка: %1$s + Пересылающий сервер: %1$s +\nОшибка сервера получателя: %2$s + Пересылающий сервер: %1$s +\nОшибка: %2$s + Предупреждение доставки сообщения + Ошибка сети - сообщение не было отправлено после многократных попыток. + Адрес сервера несовместим с настройками сети. + информация сервера об очереди: %1$s +\n +\nпоследнее полученное сообщение: %2$s + Показать список чатов в новом окне + Приложение будет запрашивать подтверждение загрузки с неизвестных серверов (за исключением .onion адресов или когда SOCKS-прокси включен). + Незащищённый + Без Тора или ВПН, Ваш IP адрес будет доступен серверам файлов. + Отправлять сообщения напрямую, когда IP адрес защищен, и Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку. + Черная + Тёмный режим + Не отправлять сообщения напрямую, даже если сервер получателя не поддерживает конфиденциальную доставку. + Режим цветов + Разрешить прямую доставку + Всегда + Подтверждать файлы с неизвестных серверов. + Всегда использовать конфиденциальную доставку. + Тёмная + Отладка доставки + Ошибка инициализации WebView. Обновите Вашу систему до новой версии. Свяжитесь с разработчиками. +\nОшибка: %s + Светлый режим + Сделайте ваши чаты разными! + Информация об очереди сообщений + Персидский интерфейс + Защитить IP адрес + Защитите ваш IP адрес от серверов сообщений, выбранных Вашими контактами. +\nВключите в настройках Сеть и серверы. + Отправьте сообщения напрямую, когда Ваш сервер или сервер получателя не поддерживает конфиденциальную доставку. + Конфиденциальная доставка + Использовать конфиденциальную доставку с неизвестными серверами. + Использовать конфиденциальную доставку с неизвестными серверами, когда IP адрес не защищен. + Когда IP защищен + Да + Чтобы защитить ваш IP адрес, приложение использует Ваши SMP серверы для конфиденциальной доставки сообщений. + Изображения профилей + Все режимы + Тема приложения + Сбросить на тему приложения + Сбросить на тему пользователя + Неизвестные серверы! + Без Тора или ВПН, Ваш IP адрес будет доступен этим серверам файлов: +\n%1$s. + Не использовать конфиденциальную маршрутизацию. + Никогда + Неизвестные серверы + Нет + Показать статус сообщения + Прямая доставка сообщений + Режим доставки сообщений + КОНФИДЕНЦИАЛЬНАЯ ДОСТАВКА СООБЩЕНИЙ + Цвета чата + Тема чата + Тема профиля + Дополнительный акцент 2 + Дополнительные настройки + Обрезать + Полностью + Добрый день! + Доброе утро! + Полученный ответ + Удалить изображение + Повторить + Сбросить цвет + Масштаб + Отправленный ответ + Установить тему по умолчанию + Рисунок обоев + Фон обоев + Применить к \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml index 5701c0ca78..f6988ca367 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/th/strings.xml @@ -855,7 +855,6 @@ เปลี่ยนกลับ บันทึก รีเซ็ตสี - บันทึกสี ได้รับ, ห้าม ผู้รับจะเห็นการอัปเดตเมื่อคุณพิมพ์ ลดการใช้แบตเตอรี่ diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml index 9ae8707c11..f25811fef7 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/tr/strings.xml @@ -87,7 +87,7 @@ Konuştuğunuz kişinin uygulamasından güvenlik kodunu okut. WebRTC ICE sunucu adreslerinin doğru formatta olduğundan emin olun: Satırlara ayrılmış ve yinelenmemiş şekilde. Kaydet - TEMA RENKLERİ + ARAYÜZ RENKLERİ Otomatik-kabul ayarlarını kaydet Ayarlar kaydedilsin mi? Kaydet ve konuştuğun kişilere bildir @@ -122,7 +122,6 @@ SimpleX Koyu tema Tema - Rengi kaydet Temayı içe aktar Temayı içe aktarırken hata oluştu Dosyanın doğru YAML sözdizimine sahip olduğundan emin olun. Tema dosyası yapısının bir örneğine sahip olmak için temayı dışa aktarın. @@ -150,7 +149,7 @@ Yetki Sesli mesajlara izin verilsin mi? Profil ekle - Üyelere direkt mesaj gönderilmesine izin ver. + Üyelere doğrudan mesaj gönderilmesine izin ver. Kendiliğinden yok olan mesajlar göndermeye izin ver. Gönderilen mesajların kalıcı olarak silinmesine izin ver. (24 saat içinde) Dosya ve medya göndermeye izin ver. @@ -506,7 +505,7 @@ Kendiliğinden şu sürede yok olacak Kendiliğinden şu sürede yok olacak: %s etkin - Direkt mesaj + Doğrudan mesajlar Devre dışı bırak Görünen ad, boşluk gibi aralıklama türleri içeremez. İsmini gir: @@ -530,7 +529,7 @@ %s üyesi için şifreleme kabul edildi doğrudan Yeniden gösterme - Bu grupta üyeler arası direkt mesajlar yasaklıdır. + Bu grupta üyeler arası doğrudan mesajlaşma yasaklıdır. konuşulan kişi için etkinleşti senin için etkinleştirildi %d sn @@ -653,7 +652,7 @@ Grup adını gir: Grup tam adı: Dosya ve medya - Grup üyeleri direkt mesaj gönderebilir. + Grup üyeleri doğrudan mesaj gönderebilir. Grup üyeleri, gönderilen mesajları kalıcı olarak silebilir. (24 saat içinde) Grup üyeleri sesli mesaj gönderebilirler. Bu toplu konuşmada, dosya ve medya yasaklanmıştır. @@ -1007,7 +1006,7 @@ Mesaj tepkilerini yasakla. Sesli mesaj göndermeyi yasakla. Geri alınamaz mesaj silme işlemini yasakla. - Üyelere direkt mesaj göndermeyi yasakla. + Üyelere doğrudan mesaj göndermeyi yasakla. Dosya ve medya göndermeyi yasakla. Canlı mesajlar Arayüz geliştirildi @@ -1216,7 +1215,7 @@ Önceki mesajın hash\'i farklı. SimpleX Kilit aktif değil! SimpleX Kilit - Direkt bağlanılsın mı? + Doğrudan bağlanılsın mı? Bu ayarlar mevcut profiliniz içindir Sunucu testi başarısız! Bağlantıyı onayla @@ -1231,7 +1230,7 @@ Bluetooth desteği ve diğer iyileştirmeler. Ayarlar AYARLAR - Bağlanmak için direkt mesaj gönderin + Bağlanmak için doğrudan mesaj gönderin Güvenlik kodu Daha hızlı gruplara katılma ve daha güvenilir mesajlar. Sohbet profiliniz grup üyelerine gönderilecek @@ -1404,7 +1403,7 @@ Yapıştırdığın bağlantı bir SimpleX bağlantısı değil. Gönderilmiş mesaj Gruplar için alıcılar devre dışı bırakılsın mı? - Aktarıcı sunucu IP adresinizi korur, ancak aramanın süresini gözlemleyebilir. + Yönlendirici sunucu IP adresinizi korur, ancak aramanın süresini gözlemleyebilir. Dosya yükleniyor Masaüstüne bağlanıyor Eklenecek kişi yok @@ -1694,7 +1693,7 @@ Uyarı: Birden fazla cihazda sohbet başlatmak desteklenmez ve mesaj iletimi başarısızlıklara neden olabilir. Veritabanı parolasını doğrulayın Parolayı doğrulayın - 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. + Tüm kişileriniz, konuşmalarınız ve dosyalarınız güvenli bir şekilde şifrelenir ve yapılandırılmış XFTP yönlendiricilerine parçalar halinde yüklenir. Arşivle ve yükle Uyarı: arşiv silinecektir.]]> Taşımak için veritabanı parolasını hatırladığınızı doğrulayın. @@ -1770,4 +1769,88 @@ Profil resimleri Profil resimlerini şekillendir Kare,daire, veya aralarında herhangi bir şey. + Kapasite aşıldı - alıcı önceden gönderilen mesajları almadı. + Hedef sunucu hatası: %1$s + Hata: %1$s + Yönlendirme sunucusu: %1$s +\nHedef sunucu hatası: %2$s + Yönlendirme sunucusu: %1$s +\nHata: %2$s + Mesaj iletimi uyarısı + Ağ sorunları - birçok gönderme denemesinden sonra mesajın süresi doldu. + Sunucu adresi ağ ayarlarıyla uyumlu değil. + Sunucu sürümü ağ ayarlarıyla uyumlu değil. + Yanlış anahtar veya bilinmeyen bağlantı - büyük olasılıkla bu bağlantı silinmiştir. + Gizli yönlendirme + Bilinmeyen yönlendiriciler + Her zaman gizli yönlendirmeyi kullan. + Gizli yönlendirmeyi KULLANMA. + Mesaj yönlendirme modu + Hiçbir zaman + Bilinmeyen sunucularla gizli yönlendirme kullan. + Sürüm düşürmeye izin ver + Hayır + Mesaj yönlendirme yedeklemesi + Her zaman + Sizin veya hedef sunucunun özel yönlendirmeyi desteklememesi durumunda bile mesajları doğrudan GÖNDERMEYİN. + IP adresi korumalı olduğunda ve sizin veya hedef sunucunun özel yönlendirmeyi desteklemediği durumlarda mesajları doğrudan gönderin. + Sizin veya hedef sunucunun özel yönlendirmeyi desteklemediği durumlarda mesajları doğrudan gönderin. + GİZLİ MESAJ YÖNLENDİRME + Mesaj durumunu göster + IP adresinizi korumak için,gizli yönlendirme mesajları iletmek için SMP sunucularınızı kullanır. + Korumasız + IP adresi korunmadığında bilinmeyen sunucularla gizli yönlendirme kullan. + IP gizliyken + Evet + Sohbet teması + Profil teması + Siyah + Renk modu + Karanlık mod renkleri + Aydınlık + Sistem + Ek vurgu 2 + Bütün renk modları + Şuna uygula + Karanlık mod + Doldur + Ölçeklendir + Gönderilen cevap + Varsayılan temaya ayarla + Gelişmiş ayarlar + Günaydın! + Karanlık + Aydınlık mod + IP adresini koru + DOSYALAR + Sohbet renkleri + Sığdır + Alınan cevap + İyi öğlenler! + Resmi kaldır + Tekrarla + Rengi sıfırla + Sohbet listesini yeni pencerede göster + Bilinmeyen sunucular! + Tor veya VPN olmadan, IP adresiniz bu XFTP yönlendiricileri tarafından görülebilir: +\n%1$s. + Tor veya VPN olmadan, IP adresiniz dosya sunucularına görülebilir. + Duvar kağıdı vurgusu + Duvar kağıdı arkaplanı + Uygulama, bilinmeyen dosya sunucularından indirmeleri onaylamanızı isteyecektir (.onion veya SOCKS vekilleri etkin değilse). + WebView başlatılırken hata oluştu. Sisteminizi yeni sürüme güncelleyin. Lütfen geliştiricilerle iletişime geçin. +\nHata: %s + Sohbetlerinizin farklı görünmesini sağlayın! + Farsça Arayüz + Kullanıcı temasına sıfırla + IP adresinizi kişileriniz tarafından seçilen mesajlaşma yönlendiricilerinden koruyun. +\n*Ağ ve sunucular* ayarlarında etkinleştirin. + Gizli mesaj yönlendirme 🚀 + Bilinmeyen sunuculardan gelen dosyaları onayla. + Geliştirilmiş mesaj iletimi + Yeni sohbet temaları + Dosyaları güvenle alın + Azaltılmış pil kullanımı ile. + Uygulama teması + Uygulama temasına sıfırla \ 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 948f91b89a..b8d802450d 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/uk/strings.xml @@ -450,7 +450,6 @@ Змінити роль у групі\? Помилка при вилученні учасника Ваш профіль чату буде відправлений учасникам групи - Зберегти колір Видалення для всіх Голосові повідомлення Голосові повідомлення заборонені в цьому чаті. @@ -742,7 +741,7 @@ Показати профіль Показати профіль чату Коли ви ділитесь анонімним профілем з кимось, цей профіль буде використовуватися для груп, до яких вас запрошують. - Світла + Світлий Помилка імпорту теми Налаштування групи Повідомлення зникнення @@ -1038,7 +1037,7 @@ Хост Порт Обов\'язково - КОЛОРИ ТЕМИ + КОЛЬОРИ ІНТЕРФЕЙСУ Створіть адресу, щоб дозволити людям підключатися до вас. Ваші контакти залишаться підключеними. Створити SimpleX-адресу @@ -1664,7 +1663,7 @@ Імпорт архіву Повторний імпорт Завершіть міграцію на іншому пристрої. - Подати заявку + Застосувати Перенести пристрій Перехід на інший пристрій Помилка експорту бази даних чату @@ -1713,4 +1712,142 @@ Використовуйте додаток під час розмови. Підтвердіть парольну фразу Ви можете спробувати ще раз. + Перевищено ліміт - одержувач не отримав раніше надіслані повідомлення. + Помилка сервера призначення: %1$s + Помилка: %1$s + Попередження про доставку повідомлення + Проблеми з мережею - термін дії повідомлення закінчився після багатьох спроб надіслати його. + Сервер переадресації: %1$s +\nПомилка сервера призначення: %2$s + Сервер переадресації: %1$s +\nПомилка: %2$s + Мікрофон + Джерело повідомлення залишається приватним. + Завжди + Завжди використовуйте приватну маршрутизацію. + Режим маршрутизації повідомлень + НЕ використовуйте приватну маршрутизацію. + Ні + НЕ надсилайте повідомлення напряму, навіть якщо ваш сервер або сервер призначення не підтримує приватну маршрутизацію. + Камера + Надати в налаштуваннях + Навушники + Стільниковий + власники + Більш надійне з\'єднання з мережею. + Керування мережею + Пересилання та збереження повідомлень + Переадресувати повідомлення… + Дозволити зниження рейтингу + Тема програми + Темний режим + Завантажити + Навушник + Увімкнено для + Повідомлення про помилку, зв\'яжіться з розробниками. + Файли та медіафайли заборонені + Знайдіть цей дозвіл у налаштуваннях Android і надайте його вручну. + Переслати + Переслано + Переслано з + Учасники групи можуть надсилати посилання SimpleX. + Звуки вхідного дзвінка + Світлий режим + Запасний варіант маршрутизації повідомлень + МАРШРУТИЗАЦІЯ ПРИВАТНИХ ПОВІДОМЛЕНЬ + переслано + Інше + Дозволити надсилати посилання SimpleX. + Заборонити надсилання посилань SimpleX + Немає підключення до мережі + Ніколи + Приватна маршрутизація + Bluetooth + Камера та мікрофон + Надайте дозвіл(и) на здійснення дзвінків + Відкрити налаштування + ФАЙЛИ + Зображення профілю + Підключення до мережі + адміністратори + всі учасники + Литовський інтерфейс + Надавати дозволи + Кольори чату + Тема чату + Тема профілю + Темна + Додатковий акцент 2 + Розширені налаштування + Усі кольорові режими + Застосувати до + Колірний режим + Темна + Кольори темного режиму + Заповнити + Підходить + Доброго дня! + Доброго ранку! + Світлий + Видалити зображення + Помилка ініціалізації WebView. Оновіть систему до нової версії. Зверніться до розробників. +\nПомилка: %s + Підтвердити файли з невідомих серверів. + Покращена доставка повідомлень + Перський інтерфейс + Маршрутизація приватних повідомлень 🚀 + Захист IP-адреси + Захистіть свою IP-адресу від ретрансляторів повідомлень, обраних вашими контактами. +\nУвімкніть у налаштуваннях *Мережа та сервери*. + Отримано відповідь + Збережено + Програма попросить підтвердити завантаження з невідомих файлових серверів (крім .onion або коли ввімкнено SOCKS-проксі). + Надсилайте повідомлення напряму, якщо IP-адреса захищена, а ваш сервер або сервер призначення не підтримує приватну маршрутизацію. + Надсилайте повідомлення напряму, якщо ваш сервер або сервер призначення не підтримує приватну маршрутизацію. + Встановлення теми за замовчуванням + Надіслано відповідь + Показати список чату в новому вікні + Використовуйте приватну маршрутизацію з невідомими серверами. + Використовуйте приватну маршрутизацію з невідомими серверами, якщо IP-адреса не захищена. + Фон шпалер + Акцент на шпалерах + Повторити + Масштаб + Нехай ваші чати виглядають інакше! + Нові теми чату + Безпечне отримання файлів + З меншим споживанням заряду акумулятора. + Неправильний ключ або невідоме з\'єднання - швидше за все, це з\'єднання видалено. + Адреса сервера несумісна з налаштуваннями мережі. + Голосові повідомлення заборонені + WiFi + Спікер + Повернутися до теми програми + Повернутися до теми користувача + Посилання SimpleX + Квадрат, коло або щось середнє між ними. + Буде ввімкнено в прямих чатах! + збережено + збережено з %s + Дротова мережа Ethernet + Невідомі сервери! + Без Tor або VPN ваша IP-адреса буде видимою для цих XFTP-ретрансляторів: +\n%1$s. + Одержувач(и) не бачить, від кого це повідомлення. + Збережено з + Серверна версія несумісна з мережевими налаштуваннями. + Посилання SimpleX заборонені + Показати статус повідомлення + Щоб захистити вашу IP-адресу, приватна маршрутизація використовує ваші SMP-сервери для доставки повідомлень. + Невідомі реле + Незахищений + Коли IP приховано + Так + Отримання паралелізму + У цій групі заборонені посилання на SimpleX. + Сформуйте зображення профілю + При підключенні аудіо та відеодзвінків. + Скинути колір + Система + Без Tor або VPN ваша IP-адреса буде видимою для файлових серверів. \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml index 939071da2b..735ef63237 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/vi/strings.xml @@ -172,4 +172,31 @@ Cả bạn và liên hệ của bạn đều có thể thả cảm xúc tin nhắn. Xin lưu ý: relay tin nhắn và tệp được kết nối thông qua SOCKS proxy. Các cuộc gọi và bản xem trước liên kết sử dụng kết nối trực tiếp.]]> Tốt cho pin. Dịch vụ nền kiểm tra tin nhắn 10 phút một lần. Bạn có thể bỏ lỡ các cuộc gọi hoặc tin nhắn khẩn cấp.]]> + Luôn luôn + đang gọi… + Theo hồ sơ trò chuyện (mặc định) hoặc theo kết nối (BETA). + cuộc gọi đang chờ + Sử dụng nhiều pin hơn! Dịch vụ chạy nền luôn luôn chạy - thông báo sẽ được hiển thị ngay khi nhận được tin nhắn.]]> + Cảnh báo: kho lưu trữ sẽ bị xóa.]]> + Cuộc gọi đang chờ + Cho phép hạ cấp + Cuộc gọi đã kết thúc! + Luôn luôn sử dụng định tuyến riêng tư. + Cuộc gọi kết thúc + cuộc gọi kết thúc %1$s + lỗi cuộc gọi + CUỘC GỌI + Hủy xem trước ảnh + Hủy xem trước tệp + Hủy + Camera + Camera hiện đang bận + Camera + Cuộc gọi trên màn hình khóa: + Đen + Áp dụng cho + Biến thể của màu sơ cấp 2 + Cài đặt nâng cao + Tất cả chế độ màu + Camera và mic \ 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 536207892d..bd9c8fa5c2 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 @@ -69,7 +69,7 @@ 总是通过中继连接 允许您的联系人不不可逆地删除已发送消息。(24小时) 联系人允许 - 仅有您的联系人许可后才允许语音消息。 + 允许语音消息,前提是你的联系人允许这样的消息。 您: %1$s 允许您的联系人发送语音消息。 始终 @@ -87,8 +87,8 @@ 删除联系人? 已删除群组 删除图片 - 仅有您的联系人许可后才允许限时消息。 - 只有您的联系人同意才允许不可逆地删除消息。(24小时) + 允许限时消息,前提是你的联系人允许这样的消息。 + 允许不可逆的消息删除,前提是你的联系人允许这样做。(24小时) 允许不可逆地删除已发送消息。(24小时) 为此删除聊天资料 删除数据库 @@ -831,7 +831,6 @@ 恢复 重置颜色 - 保存颜色 减少电池使用量 为了保护时区,图像/语音文件使用 UTC。 使用聊天 @@ -1112,13 +1111,13 @@ 只有您可以拨打电话。 只有您的联系人可以拨打电话。 允许您的联系人与您进行语音通话。 - 仅当您的联系人允许时才允许呼叫。 + 允许通话,前提是你的联系人允许它们。 禁止音频/视频通话。 1分钟 一次性链接 您和您的联系人都可以添加消息回应。 允许消息回应。 - 只有您的联系人允许时才允许消息回应。 + 允许消息回应,前提是你的联系人允许它们。 应用程序密码被替换为自毁密码。 更改自毁模式 关于 SimpleX 地址 @@ -1195,7 +1194,7 @@ 当人们请求连接时,您可以接受或拒绝它。 如果您以后删除您的地址,您不会丢失您的联系人。 用户指南中阅读更多。]]> - 主题颜色 + 界面颜色 与您的联系人保持连接。 与联系人分享 邀请朋友 @@ -1769,4 +1768,94 @@ 个人资料图 改变个人资料图形状 方形、圆形、或两者之间的任意形状 + 超出了额度 — 收信人没收到之前发送的消息。 + 目标服务器错误:%1$s + 错误:%1$s + 转发服务器:%1$s +\n错误:%2$s + 消息传输警告 + 网络问题 — 许多发送消息的尝试后,消息过期了。 + 密钥错误或连接未知 — 连接被删除的可能性最大。 + 始终 + 从不 + 未知中继 + 始终使用私密路由。 + 不使用私密路由。 + 在未知服务器上使用私密路由。 + 当 IP 地址不受保护时,在未知服务器上使用私密路由。 + 当 IP 隐藏时 + + + 当你的服务器或目标服务器不支持私密路由时直接发送消息。 + 备用消息路由 + 显示消息状态 + 为了保护你的 IP 地址,私密路由使用你的 SMP 服务器来传送消息。 + 私密消息路由 + 私密路由 + 当 IP 地址受保护且你的服务器或目标服务器不支持私密路由时,直接发送消息。 + 服务器地址和网络设置不兼容。 + 允许降级 + 服务器版本和网络设置不兼容。 + 未受保护 + 不直接发送消息,即便你的服务器或目标服务器不支持私密路由。 + 转发服务器:%1$s +\n目标服务器错误:%2$s + 消息路由模式 + 未知服务器! + 没有 Tor 或 VPN,这些 XFTP 中继可以看到你的 IP 地址: +\n%1$s. + 没有 Tor 或 VPN,文件服务器可以看到你的 IP 地址。 + 保护 IP 地址 + 文件 + 应用将请求确认来自未知服务器的下载(.onion 或启用 SOCKS 代理时除外)。 + 个人资料主题 + 在新窗口中显示聊天列表 + 所有颜色模式 + 应用到 + + 颜色模式 + 深色 + 深色模式 + 深色模式颜色 + 填充 + 适配 + 下午好! + 早上好! + 浅色 + 浅色模式 + 收到的回复 + 删除图片 + 已发送回复 + 设置默认主题 + 系统 + 壁纸强调色 + 壁纸背景色 + 额外的强调色2 + 高级设置 + 聊天颜色 + 重复 + 聊天主题 + 重置颜色 + 缩放 + Webview 初始化失败。更新你的系统到新版本。请联系开发者。 +\n错误:%s + 保护您的真实 IP 地址。不让你的联系人选择的消息中继看到它。 +\n在*网络&服务器*设置中开启。 + 确认来自未知服务器的文件。 + 安全地接收文件 + 改进了消息传递 + 让你的聊天看上去不同! + 私密消息路由🚀 + 新的聊天主题 + 波斯语用户界面 + 降低电池用量 + 主题 + 重置为应用主题 + 重置为用户主题 + 发送调试 + 消息队列信息 + 消息队列信息:%1$s +\n +\n上一则收到的信息:%2$s + \ No newline at end of file diff --git a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml index c9d4298bc7..b6434c39db 100644 --- a/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml +++ b/apps/multiplatform/common/src/commonMain/resources/MR/zh-rTW/strings.xml @@ -827,7 +827,6 @@ 儲存群組檔案時出錯 恢復 主題 - 儲存顏色 你允許 修改群組內的設定 私訊 diff --git a/apps/multiplatform/common/src/commonTest/kotlin/chat/simplex/app/ThemesTest.kt b/apps/multiplatform/common/src/commonTest/kotlin/chat/simplex/app/ThemesTest.kt new file mode 100644 index 0000000000..ae838dcff5 --- /dev/null +++ b/apps/multiplatform/common/src/commonTest/kotlin/chat/simplex/app/ThemesTest.kt @@ -0,0 +1,38 @@ +package chat.simplex.app + +import chat.simplex.common.ui.theme.* +import kotlin.test.Test +import kotlin.test.assertEquals + +// use this command for testing: +// ./gradlew desktopTest +class ThemesTest { + @Test + fun testSkipDuplicates() { + val r = ArrayList() + r.add(ThemeOverrides("UUID", DefaultTheme.DARK)) + r.add(ThemeOverrides("UUID", DefaultTheme.DARK)) + r.add(ThemeOverrides("UUID", DefaultTheme.LIGHT)) + r.add(ThemeOverrides("UUID2", DefaultTheme.DARK)) + r.add(ThemeOverrides("UUID3", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper())) + r.add(ThemeOverrides("UUID4", DefaultTheme.LIGHT, wallpaper = null)) + r.add(ThemeOverrides("UUID5", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper(preset = "something"))) + r.add(ThemeOverrides("UUID5", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper(preset = "something2"))) + r.add(ThemeOverrides("UUID6", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper(preset = "something2"))) + r.add(ThemeOverrides("UUID7", DefaultTheme.DARK, wallpaper = ThemeWallpaper(preset = "something2"))) + r.add(ThemeOverrides("UUID8", DefaultTheme.DARK, wallpaper = ThemeWallpaper(imageFile = "image"))) + r.add(ThemeOverrides("UUID9", DefaultTheme.DARK, wallpaper = ThemeWallpaper(imageFile = "image2"))) + r.add(ThemeOverrides("UUID10", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper(imageFile = "image"))) + assertEquals( + r.skipDuplicates(), listOf( + ThemeOverrides("UUID", DefaultTheme.DARK), + ThemeOverrides("UUID3", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper()), + ThemeOverrides("UUID5", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper(preset = "something")), + ThemeOverrides("UUID6", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper(preset = "something2")), + ThemeOverrides("UUID7", DefaultTheme.DARK, wallpaper = ThemeWallpaper(preset = "something2")), + ThemeOverrides("UUID8", DefaultTheme.DARK, wallpaper = ThemeWallpaper(imageFile = "image")), + ThemeOverrides("UUID10", DefaultTheme.LIGHT, wallpaper = ThemeWallpaper(imageFile = "image")) + ) + ) + } +} diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt index 7b7762eefc..4128982f78 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/DesktopApp.kt @@ -4,8 +4,7 @@ import androidx.compose.desktop.ui.tooling.preview.Preview import androidx.compose.foundation.background import androidx.compose.foundation.layout.* import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.material.MaterialTheme -import androidx.compose.material.Text +import androidx.compose.material.* import androidx.compose.runtime.* import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier @@ -85,20 +84,25 @@ private fun ApplicationScope.AppWindow(closedByError: MutableState) { position = WindowPosition(state.x.dp, state.y.dp) ) + val storingJob: MutableState = remember { mutableStateOf(Job()) } LaunchedEffect( windowState.position.x.value, windowState.position.y.value, windowState.size.width.value, windowState.size.height.value ) { - storeWindowState( - WindowPositionSize( - x = windowState.position.x.value.toInt(), - y = windowState.position.y.value.toInt(), - width = windowState.size.width.value.toInt(), - height = windowState.size.height.value.toInt() + storingJob.value.cancel() + storingJob.value = launch { + delay(1000L) + storeWindowState( + WindowPositionSize( + x = windowState.position.x.value.toInt(), + y = windowState.position.y.value.toInt(), + width = windowState.size.width.value.toInt(), + height = windowState.size.height.value.toInt() + ) ) - ) + } } simplexWindowState.windowState = windowState @@ -111,6 +115,7 @@ private fun ApplicationScope.AppWindow(closedByError: MutableState) { false } }, title = "SimpleX") { +// val hardwareAccelerationDisabled = remember { listOf(GraphicsApi.SOFTWARE_FAST, GraphicsApi.SOFTWARE_COMPAT, GraphicsApi.UNKNOWN).contains(window.renderApi) } simplexWindowState.window = window AppScreen() if (simplexWindowState.openDialog.isAwaiting) { diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/model/NtfManager.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/model/NtfManager.desktop.kt index 3fab849361..3913c0dc9b 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/model/NtfManager.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/model/NtfManager.desktop.kt @@ -8,6 +8,8 @@ import chat.simplex.common.views.call.RcvCallInvitation import chat.simplex.common.views.helpers.* import chat.simplex.res.MR import com.sshtools.twoslices.* +import kotlinx.coroutines.sync.Mutex +import kotlinx.coroutines.sync.withLock import java.awt.* import java.awt.TrayIcon.MessageType import java.io.File @@ -15,6 +17,7 @@ import javax.imageio.ImageIO object NtfManager { private val prevNtfs = arrayListOf>() + private val prevNtfsMutex: Mutex = Mutex() fun notifyCallInvitation(invitation: RcvCallInvitation): Boolean { if (simplexWindowState.windowFocused.value) return false @@ -55,21 +58,29 @@ object NtfManager { fun hasNotificationsForChat(chatId: ChatId) = false//prevNtfs.any { it.first == chatId } fun cancelNotificationsForChat(chatId: ChatId) { - val ntf = prevNtfs.firstOrNull { it.first == chatId } - if (ntf != null) { - prevNtfs.remove(ntf) - /*try { - ntf.second.close() - } catch (e: Exception) { - // Can be java.lang.UnsupportedOperationException, for example. May do nothing - println("Failed to close notification: ${e.stackTraceToString()}") - }*/ + withBGApi { + prevNtfsMutex.withLock { + val ntf = prevNtfs.firstOrNull { it.first == chatId } + if (ntf != null) { + prevNtfs.remove(ntf) + /*try { + ntf.second.close() + } catch (e: Exception) { + // Can be java.lang.UnsupportedOperationException, for example. May do nothing + println("Failed to close notification: ${e.stackTraceToString()}") + }*/ + } + } } } fun cancelAllNotifications() { // prevNtfs.forEach { try { it.second.close() } catch (e: Exception) { println("Failed to close notification: ${e.stackTraceToString()}") } } - prevNtfs.clear() + withBGApi { + prevNtfsMutex.withLock { + prevNtfs.clear() + } + } } fun displayNotification(user: UserLike, chatId: String, displayName: String, msgText: String, image: String?, actions: List Unit>>) { @@ -110,7 +121,11 @@ object NtfManager { builder.action(it.first, it.second) } try { - prevNtfs.add(chatId to builder.toast()) + withBGApi { + prevNtfsMutex.withLock { + prevNtfs.add(chatId to builder.toast()) + } + } } catch (e: Throwable) { Log.e(TAG, e.stackTraceToString()) if (e !is Exception) { diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt index 71f862b30a..33a3ae2578 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt @@ -26,7 +26,7 @@ fun initApp() { } applyAppLocale() if (DatabaseUtils.ksSelfDestructPassword.get() == null) { - initChatControllerAndRunMigrations() + initChatControllerOnStart() } // LALAL //testCrypto() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt index 9b2368fcd3..5f33e2a943 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Files.desktop.kt @@ -13,8 +13,10 @@ actual val dataDir: File = File(desktopPlatform.dataPath) actual val tmpDir: File = File(System.getProperty("java.io.tmpdir") + File.separator + "simplex").also { it.deleteOnExit() } actual val filesDir: File = File(dataDir.absolutePath + File.separator + "simplex_v1_files") actual val appFilesDir: File = filesDir +actual val wallpapersDir: File = File(dataDir.absolutePath + File.separator + "simplex_v1_assets" + File.separator + "wallpapers").also { it.mkdirs() } actual val coreTmpDir: File = File(dataDir.absolutePath + File.separator + "tmp") actual val dbAbsolutePrefixPath: String = dataDir.absolutePath + File.separator + "simplex_v1" +actual val preferencesDir = File(desktopPlatform.configPath).also { it.parentFile.mkdirs() } actual val chatDatabaseFileName: String = "simplex_v1_chat.db" actual val agentDatabaseFileName: String = "simplex_v1_agent.db" diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt index b758988227..a966c0a4e2 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/Resources.desktop.kt @@ -1,12 +1,17 @@ package chat.simplex.common.platform import androidx.compose.runtime.* +import androidx.compose.ui.graphics.ImageBitmap +import androidx.compose.ui.graphics.toComposeImageBitmap import androidx.compose.ui.text.font.Font import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.font.FontWeight import androidx.compose.ui.unit.* import chat.simplex.common.simplexWindowState +import chat.simplex.common.views.helpers.* +import com.jthemedetecor.OsThemeDetector import com.russhwolf.settings.* +import dev.icerock.moko.resources.ImageResource import dev.icerock.moko.resources.StringResource import dev.icerock.moko.resources.desc.desc import java.io.File @@ -18,7 +23,15 @@ actual fun font(name: String, res: String, weight: FontWeight, style: FontStyle) actual fun StringResource.localized(): String = desc().toString() -actual fun isInNightMode() = false +private val detector: OsThemeDetector = OsThemeDetector.getDetector() +actual fun isInNightMode() = try { + detector.isDark +} +catch (e: Exception) { + Log.e(TAG, e.stackTraceToString()) + /* On Mac this code can produce exception */ + false +} private val settingsFile = File(desktopPlatform.configPath + File.separator + "settings.properties") @@ -26,15 +39,28 @@ private val settingsFile = private val settingsThemesFile = File(desktopPlatform.configPath + File.separator + "themes.properties") .also { it.parentFile.mkdirs() } + private val settingsProps = Properties() - .also { try { it.load(settingsFile.reader()) } catch (e: Exception) { Properties() } } + .also { props -> + if (!settingsFile.exists()) return@also + + try { + settingsFile.reader().use { + // Force exception to happen + //it.close() + props.load(it) + } + } catch (e: Exception) { + Log.e(TAG, "Error reading settings file: ${e.stackTraceToString()}") + } + } private val settingsThemesProps = Properties() - .also { try { it.load(settingsThemesFile.reader()) } catch (e: Exception) { Properties() } } + .also { props -> try { settingsThemesFile.reader().use { props.load(it) } } catch (e: Exception) { /**/ } } -actual val settings: Settings = PropertiesSettings(settingsProps) { settingsProps.store(settingsFile.writer(), "") } -actual val settingsThemes: Settings = PropertiesSettings(settingsThemesProps) { settingsThemesProps.store(settingsThemesFile.writer(), "") } +actual val settings: Settings = PropertiesSettings(settingsProps) { withApi { settingsFile.writer().use { settingsProps.store(it, "") } } } +actual val settingsThemes: Settings = PropertiesSettings(settingsThemesProps) { withApi { settingsThemesFile.writer().use { settingsThemesProps.store(it, "") } } } actual fun windowOrientation(): WindowOrientation = if (simplexWindowState.windowState.size.width > simplexWindowState.windowState.size.height) { @@ -58,3 +84,6 @@ actual fun isRtl(text: CharSequence): Boolean { dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT || dir == Character.DIRECTIONALITY_RIGHT_TO_LEFT_ARABIC } } + +actual fun ImageResource.toComposeImageBitmap(): ImageBitmap? = + image.toComposeImageBitmap() diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt index 358c20d769..d7dc1ca859 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/ui/theme/Theme.desktop.kt @@ -10,6 +10,10 @@ private val detector: OsThemeDetector = OsThemeDetector.getDetector() registerListener(::reactOnDarkThemeChanges) } +// TODO: explore possibility to use +//@Composable +//actual fun isSystemInDarkTheme(): Boolean = androidx.compose.foundation.isSystemInDarkTheme() + @Composable actual fun isSystemInDarkTheme(): Boolean = try { detector.isDark 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 d3bf1bf01e..d6331616cc 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 @@ -25,11 +25,6 @@ val connections = ArrayList() @Composable actual fun ActiveCallView() { - val endCall = { - val call = chatModel.activeCall.value - if (call != null) withBGApi { chatModel.callManager.endCall(call) } - } - BackHandler(onBack = endCall) val scope = rememberCoroutineScope() WebRTCController(chatModel.callCommand) { apiMsg -> Log.d(TAG, "received from WebRTCController: $apiMsg") 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 4e4846bc9f..669dd1949d 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,34 +4,23 @@ import SectionBottomSpacer import SectionDividerSpaced import SectionView import androidx.compose.foundation.layout.* -import androidx.compose.runtime.Composable -import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.* import androidx.compose.runtime.saveable.rememberSaveable 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.platform.* import chat.simplex.common.views.helpers.* -import chat.simplex.common.views.usersettings.AppearanceScope.ColorEditor import chat.simplex.res.MR import dev.icerock.moko.resources.compose.stringResource import kotlinx.coroutines.delay import java.util.Locale @Composable -actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit)) { +actual fun AppearanceView(m: ChatModel) { AppearanceScope.AppearanceLayout( m.controller.appPrefs.appLanguage, m.controller.appPrefs.systemDarkTheme, - showSettingsModal = showSettingsModal, - editColor = { name, initialColor -> - ModalManager.start.showModalCloseable { close -> - ColorEditor(name, initialColor, close) - } - }, ) } @@ -39,8 +28,6 @@ actual fun AppearanceView(m: ChatModel, showSettingsModal: (@Composable (ChatMod fun AppearanceScope.AppearanceLayout( languagePref: SharedPreference, systemDarkTheme: SharedPreference, - showSettingsModal: (@Composable (ChatModel) -> Unit) -> (() -> Unit), - editColor: (ThemeColor, Color) -> Unit, ) { ColumnWithScrollBar( Modifier.fillMaxWidth(), @@ -63,10 +50,11 @@ fun AppearanceScope.AppearanceLayout( } } SectionDividerSpaced(maxTopPadding = true) - ProfileImageSection() + ThemesSection(systemDarkTheme) SectionDividerSpaced(maxTopPadding = true) - ThemesSection(systemDarkTheme, showSettingsModal, editColor) + ProfileImageSection() + SectionBottomSpacer() } } 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 7171f17991..f69cf817e5 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 @@ -3,7 +3,6 @@ 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 @@ -17,7 +16,10 @@ import kotlinx.coroutines.* import java.io.File fun main() { + // Disable hardware acceleration + //System.setProperty("skiko.renderApi", "SOFTWARE") initHaskell() + runMigrations() initApp() tmpDir.deleteRecursively() tmpDir.mkdir() diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties index 05d63578e7..6fa11a1bbc 100644 --- a/apps/multiplatform/gradle.properties +++ b/apps/multiplatform/gradle.properties @@ -26,11 +26,11 @@ android.enableJetifier=true kotlin.mpp.androidSourceSetLayoutVersion=2 kotlin.jvm.target=11 -android.version_name=5.8-beta.1 -android.version_code=209 +android.version_name=5.8-beta.5 +android.version_code=218 -desktop.version_name=5.8-beta.1 -desktop.version_code=46 +desktop.version_name=5.8-beta.5 +desktop.version_code=52 kotlin.version=1.9.23 gradle.plugin.version=8.2.0 diff --git a/apps/simplex-directory-service/src/Directory/Events.hs b/apps/simplex-directory-service/src/Directory/Events.hs index 87950ecce7..31a7e94aad 100644 --- a/apps/simplex-directory-service/src/Directory/Events.hs +++ b/apps/simplex-directory-service/src/Directory/Events.hs @@ -35,8 +35,10 @@ import Simplex.Chat.Messages.CIContent import Simplex.Chat.Protocol (MsgContent (..)) import Simplex.Chat.Types import Simplex.Chat.Types.Shared +import Simplex.Messaging.Agent.Protocol (AgentErrorType (..)) import Simplex.Messaging.Encoding.String -import Simplex.Messaging.Util ((<$?>)) +import Simplex.Messaging.Protocol (BrokerErrorType (..)) +import Simplex.Messaging.Util (tshow, (<$?>)) data DirectoryEvent = DEContactConnected Contact @@ -53,6 +55,7 @@ data DirectoryEvent | DEItemEditIgnored Contact | DEItemDeleteIgnored Contact | DEContactCommand Contact ChatItemId ADirectoryCmd + | DELogChatResponse Text deriving (Show) crDirectoryEvent :: ChatResponse -> Maybe DirectoryEvent @@ -77,6 +80,13 @@ crDirectoryEvent = \case where ciId = chatItemId' ci err = ADC SDRUser DCUnknownCommand + CRMessageError {severity, errorMessage} -> Just $ DELogChatResponse $ "message error: " <> severity <> ", " <> errorMessage + CRChatCmdError {chatError} -> Just $ DELogChatResponse $ "chat cmd error: " <> tshow chatError + CRChatError {chatError} -> case chatError of + ChatErrorAgent {agentError = BROKER _ NETWORK} -> Nothing + ChatErrorAgent {agentError = BROKER _ TIMEOUT} -> Nothing + _ -> Just $ DELogChatResponse $ "chat error: " <> tshow chatError + CRChatErrors {chatErrors} -> Just $ DELogChatResponse $ "chat errors: " <> T.intercalate ", " (map tshow chatErrors) _ -> Nothing data DirectoryRole = DRUser | DRSuperUser diff --git a/apps/simplex-directory-service/src/Directory/Service.hs b/apps/simplex-directory-service/src/Directory/Service.hs index eefb1f77a4..a61e405cb8 100644 --- a/apps/simplex-directory-service/src/Directory/Service.hs +++ b/apps/simplex-directory-service/src/Directory/Service.hs @@ -102,6 +102,7 @@ directoryService st DirectoryOpts {superUsers, serviceName, searchResults, testi case sUser of SDRUser -> deUserCommand env ct ciId cmd SDRSuperUser -> deSuperUserCommand ct ciId cmd + DELogChatResponse r -> logInfo r where withSuperUsers action = void . forkIO $ forM_ superUsers $ \KnownContact {contactId} -> action contactId notifySuperUsers s = withSuperUsers $ \contactId -> sendMessage' cc contactId s diff --git a/apps/simplex-directory-service/src/Directory/Store.hs b/apps/simplex-directory-service/src/Directory/Store.hs index 5082cab2ce..c810102e08 100644 --- a/apps/simplex-directory-service/src/Directory/Store.hs +++ b/apps/simplex-directory-service/src/Directory/Store.hs @@ -39,8 +39,8 @@ import Data.Text (Text) import Simplex.Chat.Types import Simplex.Messaging.Encoding.String import Simplex.Messaging.Util (ifM) -import System.IO (Handle, IOMode (..), openFile, BufferMode (..), hSetBuffering) -import System.Directory (renameFile, doesFileExist) +import System.Directory (doesFileExist, renameFile) +import System.IO (BufferMode (..), Handle, IOMode (..), hSetBuffering, openFile) data DirectoryStore = DirectoryStore { groupRegs :: TVar [GroupReg], @@ -112,7 +112,7 @@ addGroupReg st ct GroupInfo {groupId} grStatus = do let ugrId = 1 + foldl' maxUgrId 0 grs grData' = grData {userGroupRegId_ = ugrId} gr' = gr {userGroupRegId = ugrId} - in (grData', gr' : grs) + in (grData', gr' : grs) ctId = contactId' ct maxUgrId mx GroupReg {dbContactId, userGroupRegId} | dbContactId == ctId && userGroupRegId > mx = userGroupRegId @@ -311,14 +311,15 @@ readDirectoryData f = Right r -> case r of GRCreate gr@GroupRegData {dbGroupId_ = gId} -> do when (isJust $ M.lookup gId m) $ - putStrLn $ "Warning: duplicate group with ID " <> show gId <> ", group replaced." + putStrLn $ + "Warning: duplicate group with ID " <> show gId <> ", group replaced." pure $ M.insert gId gr m GRUpdateStatus gId groupRegStatus_ -> case M.lookup gId m of Just gr -> pure $ M.insert gId gr {groupRegStatus_} m - Nothing -> m <$ putStrLn ("Warning: no group with ID " <> show gId <>", status update ignored.") + Nothing -> m <$ putStrLn ("Warning: no group with ID " <> show gId <> ", status update ignored.") GRUpdateOwner gId grOwnerId -> case M.lookup gId m of Just gr -> pure $ M.insert gId gr {dbOwnerMemberId_ = Just grOwnerId} m - Nothing -> m <$ putStrLn ("Warning: no group with ID " <> show gId <>", owner update ignored.") + Nothing -> m <$ putStrLn ("Warning: no group with ID " <> show gId <> ", owner update ignored.") writeDirectoryData :: FilePath -> [GroupRegData] -> IO Handle writeDirectoryData f grs = do diff --git a/blog/20240601-protecting-children-safety-requires-e2e-encryption.md b/blog/20240601-protecting-children-safety-requires-e2e-encryption.md new file mode 100644 index 0000000000..39a047f93f --- /dev/null +++ b/blog/20240601-protecting-children-safety-requires-e2e-encryption.md @@ -0,0 +1,32 @@ +--- +layout: layouts/article.html +title: "Protecting Children's Safety Requires End-to-End Encryption" +date: 2024-06-01 +previewBody: blog_previews/20240601.html +image: images/20240601-eu-privacy.png +permalink: "/blog/20240601-protecting-children-safety-requires-e2e-encryption.html" +--- + +# Protecting Children's Safety Requires End-to-End Encryption + +As lawmakers grapple with the serious issue of child exploitation online, some proposed solutions would fuel the very problem they aim to solve. Despite expert warnings, the Belgian Presidency persists in pushing for the implementation of client-side scanning on encrypted messaging services, rebranding the effort as "upload moderation". Their [latest proposal](https://eur-lex.europa.eu/legal-content/EN/TXT/?uri=COM%3A2022%3A209%3AFIN&qid=1652451192472) mandates that providers of private communication services obtain user consent for AI-based scanning of their private chats. If users do not consent, they will be prohibited from sharing images, videos, and URLs. + +Privacy critics have long pushed for measures like centralized scanning of private photos and messaging data, arguing it could detect illicit content. However, invasive monitoring of private communications would create detrimental risks that far outweigh any perceived benefits. + +## Why we’re taking action + +SimpleX Chat signed a [joint statement](https://www.globalencryption.org/2024/05/joint-statement-on-the-dangers-of-the-may-2024-council-of-the-eu-compromise-proposal-on-eu-csam/) about the dangers of the EU compromise proposal on EU CSAM because maintaining end-to-end encryption is crucial for protecting privacy and security for everyone, including and especially children.  + +We urge the Ministers in the Council of the EU to stand firm against any scanning proposals that undermine end-to-end encryption, which would enable mass surveillance and misuse by bad actors, whether framed as client-side scanning, upload moderation, or any other terminology. Compromising this basic principle opens the door to devastating privacy violations. We also urge any organizations or individuals reading this to write to their representatives and voice their concerns. European Digital Rights has [outlined these issues](https://edri.org/our-work/be-scanned-or-get-banned/) in greater detail for anyone seeking more information. + +## Why compromising privacy endangers children + +The core issue is that compromising encryption and privacy makes innocent people vulnerable to malicious hackers and criminals seeking to exploit users data. Centralized scanning systems become a tempting target, potentially exposing millions of private family photos when breached. This would easily open up avenues for blackmail, abuse, and victimization of children. A case in point is the recent [criminal charges](https://techcrunch.com/2024/01/17/unredacted-meta-documents-reveal-historical-reluctance-to-protect-children-new-mexico-lawsuit/) against Meta in New Mexico, which highlights how the tech giant's algorithms enabled child exploitation by encouraging connections between minors and sexual predators. Privacy-eroding initiatives like client-side scanning would play into the hands of malicious actors by making more sensitive information accessible and weaponized in the same way that it has been on Meta platforms. + +## What should be done + +Rather than undermining privacy, to achieve child safety online users should be empowered with high standards for encryption and data control. For example, adopting a model where children (and users in general) cannot be discovered or approached on networks unless they or their parents permit it, similar to the SimpleX network privacy model. Intelligent multi-device synchronization could enable this oversight without compromising end-to-end encryption overall. It’s always possible to protect children without opening everyone, especially children themselves, to greater vulnerabilities due to such proposals. + +However, some recent legislative efforts have bizarrely moved in the opposite direction by seeking to limit parental access. The chilling truth is that the least private platforms have been major enablers of child exploitation. Eroding privacy protections on other services will only aid criminals further, not protect children. Preserving strong encryption and user privacy must be the foundation for any credible effort to combat online child exploitation. Initiatives trading privacy for supposed safety are not just technically flawed, but would achieve the exact opposite of their stated intent. We must avoid being gaslighted by narratives that defy logic, and instead provide users with the highest possible standards for privacy protections as a core principle. + +Protecting end-to-end encryption without carving out backdoors or vulnerabilities should be non-negotiable for children's and everyone’s safety. It is critical to redirect the discourse to focus on taking genuine privacy further by protecting against [metadata hoarding](https://simplex.chat/blog/20240416-dangers-of-metadata-in-messengers.html) and other means by which people’s data can be abused or subjected to surveillance. diff --git a/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md b/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md new file mode 100644 index 0000000000..b9c4e74237 --- /dev/null +++ b/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.md @@ -0,0 +1,18 @@ +--- +layout: layouts/article.html +title: "SimpleX network: private message routing, v5.8 released with IP address protection and chat themes" +date: 2024-06-04 +# previewBody: blog_previews/20240426.html +draft: true +# image: images/20240426-profile.png +# imageBottom: true +permalink: "/blog/20240604-simplex-chat-v5.8-private-message-routing-chat-themes.html" +--- + +# SimpleX network: private message routing, v5.8 released with IP address protection and chat themes + +**Published:** June 4, 2024 + +TODO + +This is a permalink for release announcement. diff --git a/blog/README.md b/blog/README.md index a5f3d60b2e..a040833712 100644 --- a/blog/README.md +++ b/blog/README.md @@ -1,5 +1,11 @@ # Blog +Jun 1, 2024 [Children's Safety Requires End-to-End Encryption](./20240601-children-safety-requires-e2e-encryption.md) + +As lawmakers grapple with the serious issue of child exploitation online, some proposed solutions would fuel the very problem they aim to solve. + +--- + May 16, 2024 [SimpleX: Redefining Privacy by Making Hard Choices](./20240516-simplex-redefining-privacy-hard-choices.md) When it comes to open source privacy tools, the status quo often dictates the limitations of existing protocols and structures. However, these norms need to be challenged to radically shift how we approach genuinely private communication. This requires doing some uncomfortable things, like making hard choices as it relates to funding, alternative decentralization models, doubling down on privacy over convenience, and more. diff --git a/blog/images/20240601-eu-privacy.png b/blog/images/20240601-eu-privacy.png new file mode 100644 index 0000000000..4ae1a17e30 Binary files /dev/null and b/blog/images/20240601-eu-privacy.png differ diff --git a/cabal.project b/cabal.project index 190c6e06e0..6bd63f3bb2 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: 2e5433676eaa5de93ed1ea9726706b9633308477 + tag: 2e4f507919dd3a08e5c4106fde193fee4414de3c source-repository-package type: git diff --git a/docs/SERVER.md b/docs/SERVER.md index c29a805452..b30d4212f5 100644 --- a/docs/SERVER.md +++ b/docs/SERVER.md @@ -1,9 +1,32 @@ --- title: Hosting your own SMP Server -revision: 31.07.2023 +revision: 28.05.2024 --- -| Updated 05.06.2023 | Languages: EN, [FR](/docs/lang/fr/SERVER.md), [CZ](/docs/lang/cs/SERVER.md), [PL](/docs/lang/pl/SERVER.md) | +| Updated 28.05.2024 | Languages: EN, [FR](/docs/lang/fr/SERVER.md), [CZ](/docs/lang/cs/SERVER.md), [PL](/docs/lang/pl/SERVER.md) | + +### Table of Contents + +- [Hosting your own SMP server](#hosting-your-own-smp-server) + - [Overview](#overview) + - [Installation](#installation) + - [Configuration](#configuration) + - [Interactively](#interactively) + - [Via command line options](#via-command-line-options) + - [Further configuration](#further-configuration) + - [Server security](#server-security) + - [Initialization](#initialization) + - [Private keys](#private-keys) + - [Online certificate rotation](#online-certificate-rotation) + - [Tor: installation and configuration](#tor-installation-and-configuration) + - [Installation for onion address](#installation-for-onion-address) + - [SOCKS port for SMP PROXY](#socks-port-for-smp-proxy) + - [Documentation](#documentation) + - [SMP server address](#smp-server-address) + - [Systemd commands](#systemd-commands) + - [Monitoring](#monitoring) + - [Updating your SMP server](#updating-your-smp-server) + - [Configuring the app to use the server](#configuring-the-app-to-use-the-server) # Hosting your own SMP Server @@ -13,7 +36,7 @@ SMP server is the relay server used to pass messages in SimpleX network. SimpleX SimpleX clients only determine which server is used to receive the messages, separately for each contact (or group connection with a group member), and these servers are only temporary, as the delivery address can change. -_Please note_: when you change the servers in the app configuration, it only affects which server will be used for the new contacts, the existing contacts will not automatically move to the new servers, but you can move them manually using ["Change receiving address"](../blog/20221108-simplex-chat-v4.2-security-audit-new-website.md#change-your-delivery-address-beta) button in contact/member information pages – it will be automated soon. +_Please note_: when you change the servers in the app configuration, it only affects which servers will be used for the new contacts, the existing contacts will not automatically move to the new servers, but you can move them manually using ["Change receiving address"](../blog/20221108-simplex-chat-v4.2-security-audit-new-website.md#change-your-delivery-address-beta) button in contact/member information pages – it will be automated in the future. ## Installation @@ -22,7 +45,7 @@ _Please note_: when you change the servers in the app configuration, it only aff - Manual deployment (see below) - Semi-automatic deployment: - - [Offical installation script](https://github.com/simplex-chat/simplexmq#using-installation-script) + - [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/) @@ -30,7 +53,7 @@ Manual installation requires some preliminary actions: 1. Install binary: - - Using offical binaries: + - Using pre-compiled 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 && chmod +x /usr/local/bin/smp-server @@ -85,86 +108,6 @@ Manual installation requires some preliminary actions: And execute `sudo systemctl daemon-reload`. -## Tor installation - -smp-server can also be deployed to serve from [tor](https://www.torproject.org) network. Run the following commands as `root` user. - -1. Install tor: - - We're assuming you're using Ubuntu/Debian based distributions. If not, please refer to [offical tor documentation](https://community.torproject.org/onion-services/setup/install/) or your distribution guide. - - - Configure offical Tor PPA repository: - - ```sh - CODENAME="$(lsb_release -c | awk '{print $2}')" - echo "deb [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org ${CODENAME} main - deb-src [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org ${CODENAME} main" > /etc/apt/sources.list.d/tor.list - ``` - - - Import repository key: - - ```sh - curl --proto '=https' --tlsv1.2 -sSf https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --dearmor | tee /usr/share/keyrings/tor-archive-keyring.gpg >/dev/null - ``` - - - Update repository index: - - ```sh - apt update - ``` - - - Install `tor` package: - - ```sh - apt install -y tor deb.torproject.org-keyring - ``` - -2. Configure tor: - - - File configuration: - - Open tor configuration with your editor of choice (`nano`,`vim`,`emacs`,etc.): - - ```sh - vim /etc/tor/torrc - ``` - - And insert the following lines to the bottom of configuration. Please note lines starting with `#`: this is comments about each individual options. - - ```sh - # Enable log (otherwise, tor doesn't seemd to deploy onion address) - Log notice file /var/log/tor/notices.log - # Enable single hop routing (2 options below are dependencies of third). Will reduce latency in exchange of anonimity (since tor runs alongside smp-server and onion address will be displayed in clients, this is totally fine) - SOCKSPort 0 - HiddenServiceNonAnonymousMode 1 - HiddenServiceSingleHopMode 1 - # smp-server hidden service host directory and port mappings - HiddenServiceDir /var/lib/tor/simplex-smp/ - HiddenServicePort 5223 localhost:5223 - ``` - - - Create directories: - - ```sh - mkdir /var/lib/tor/simplex-smp/ && chown debian-tor:debian-tor /var/lib/tor/simplex-smp/ && chmod 700 /var/lib/tor/simplex-smp/ - ``` - -3. Start tor: - - Enable `systemd` service and start tor. Offical `tor` is a bit flunky on the first start and may not create onion host address, so we're restarting it just in case. - - ```sh - systemctl enable tor && systemctl start tor && systemctl restart tor - ``` - -4. Display onion host: - - Execute the following command to display your onion host address: - - ```sh - cat /var/lib/tor/simplex-smp/hostname - ``` - ## Configuration To see which options are available, execute `smp-server` without flags: @@ -205,11 +148,11 @@ There are several options to consider: Enter `y` to enable logging statistics in CSV format, e.g. they can be used to show aggregate usage charts in `Grafana`. -These statistics include daily counts of created, secured and deleted queues, sent and received messages, and also daily, weekly, and monthly counts of active queues (that is, the queues that were used for any messages). We believe that this information does not include anything that would allow correlating different queues as belonging to the same users, but please let us know, confidentially, if you believe that this can be exploited in any way. +These statistics include daily counts of created, secured and deleted queues, sent and received messages, and also daily, weekly, and monthly counts of active queues (that is, the queues that were used for any messages). We believe that this information does not include anything that would allow correlating different queues as belonging to the same users, but please [let us know](./SECURITY.md), confidentially, if you believe that this can be exploited in any way. - `Require a password to create new messaging queues?` - Enter `r` or your arbitrary password to password-protect `smp-server`, or `n` to disable password protection. + Press `Enter` or enter your arbitrary password to password-protect `smp-server`, or `n` to disable password protection. - `Enter server FQDN or IP address for certificate (127.0.0.1):` @@ -277,7 +220,328 @@ Fingerprint: d5fcsc7hhtPpexYUbI2XPxDbyU2d3WsVmROimcL90ss= Server address: smp://d5fcsc7hhtPpexYUbI2XPxDbyU2d3WsVmROimcL90ss=:V8ONoJ6ICwnrZnTC_QuSHfCEYq53uLaJKQ_oIC6-ve8=@ ``` -The server address above should be used in your client configuration and if you added server password it should only be shared with the other people when you want to allow them to use your server to receive the messages (all your contacts will be able to send messages, as it does not require a password). If you passed IP address or hostnames during the initialisation, they will be printed as part of server address, otherwise replace `` with the actual server addresses. +The server address above should be used in your client configuration, and if you added server password it should only be shared with the other people who you want to allow using your server to receive the messages (all your contacts will be able to send messages - it does not require a password). If you passed IP address or hostnames during the initialisation, they will be printed as part of server address, otherwise replace `` with the actual server hostnames. + +## Further configuration + +All generated configuration, along with a description for each parameter, is available inside configuration file in `/etc/opt/simplex/smp-server.ini` for further customization. Depending on the smp-server version, the configuration file looks something like this: + +```ini +[STORE_LOG] +# The server uses STM memory for persistence, +# that will be lost on restart (e.g., as with redis). +# This option enables saving memory to append only log, +# and restoring it when the server is started. +# Log is compacted on start (deleted objects are removed). +enable: on + +# Undelivered messages are optionally saved and restored when the server restarts, +# they are preserved in the .bak file until the next restart. +restore_messages: on +expire_messages_days: 21 + +# Log daily server statistics to CSV file +log_stats: on + +[AUTH] +# Set new_queues option to off to completely prohibit creating new messaging queues. +# This can be useful when you want to decommission the server, but not all connections are switched yet. +new_queues: on + +# Use create_password option to enable basic auth to create new messaging queues. +# The password should be used as part of server address in client configuration: +# smp://fingerprint:password@host1,host2 +# The password will not be shared with the connecting contacts, you must share it only +# with the users who you want to allow creating messaging queues on your server. +# create_password: password to create new queues (any printable ASCII characters without whitespace, '@', ':' and '/') + +[TRANSPORT] +# host is only used to print server address on start +host: +port: 5223 +log_tls_errors: off +websockets: off +# control_port: 5224 + +[PROXY] +# Network configuration for SMP proxy client. +# `host_mode` can be 'public' (default) or 'onion'. +# It defines prefferred hostname for destination servers with multiple hostnames. +# host_mode: public +# required_host_mode: off + +# The domain suffixes of the relays you operate (space-separated) to count as separate proxy statistics. +# own_server_domains: + +# SOCKS proxy port for forwarding messages to destination servers. +# You may need a separate instance of SOCKS proxy for incoming single-hop requests. +# socks_proxy: localhost:9050 + +# `socks_mode` can be 'onion' for SOCKS proxy to be used for .onion destination hosts only (default) +# or 'always' to be used for all destination hosts (can be used if it is an .onion server). +# socks_mode: onion + +# Limit number of threads a client can spawn to process proxy commands in parrallel. +# client_concurrency: 32 + +[INACTIVE_CLIENTS] +# TTL and interval to check inactive clients +disconnect: off +# ttl: 43200 +# check_interval: 3600 +``` + +## Server security + +### Initialization + +Although it's convenient to initialize smp-server configuration directly on the server, operators **ARE ADVISED** to initialize smp-server fully offline to protect your SMP server CA private key. + +Follow the steps to quickly initialize the server offline: + +1. Install Docker on your system. + +2. Deploy [smp-server](https://github.com/simplex-chat/simplexmq#using-docker) locally. + +3. Destroy the container. All relevant configuration files and keys will be available at `$HOME/simplex/smp/config`. + +4. Move your `CA` private key (`ca.key`) to the safe place. For further explanation, see the next section: [Server security: Private keys](#private-keys). + +5. Copy all other configuration files **except** the CA key to the server: + + ```sh + rsync -hzasP $HOME/simplex/smp/config/ @:/etc/opt/simplex/ + ``` + +### Private keys + +Connection to the smp server occurs via a TLS connection. During the TLS handshake, the client verifies smp-server CA and server certificates by comparing its fingerprint with the one included in server address. If server TLS credential is compromised, this key can be used to sign a new one, keeping the same server identity and established connections. In order to protect your smp-server from bad actors, operators **ARE ADVISED** to move CA private key to a safe place. That could be: + +- [Tails](https://tails.net/) live usb drive with [persistent and encrypted storage](https://tails.net/doc/persistent_storage/create/index.en.html). +- Offline Linux laptop. +- Bitwarden. +- Any other safe storage that satisfy your security requirements. + +Follow the steps to secure your CA keys: + +1. Login to your server via SSH. + +2. Copy the CA key to a safe place from this file: + + ```sh + /etc/opt/simplex/ca.key + ``` + +3. Delete the CA key from the server. **Please make sure you've saved you CA key somewhere safe. Otherwise, you would lose the ability to [rotate the online certificate](#online-certificate-rotation)**: + + ```sh + rm /etc/opt/simplex/ca.key + ``` + +### Online certificate rotation + +Operators of smp servers **ARE ADVISED** to rotate online certificate regularly (e.g., every 3 months). In order to do this, follow the steps: + +1. Create relevant folders: + + ```sh + mkdir -p $HOME/simplex/smp/config + ``` + +1. Copy the configuration files from the server to the local machine (if not yet): + + ```sh + rsync -hzasP @:/etc/opt/simplex/ $HOME/simplex/smp/config/ + ``` + +2. **Copy** your CA private key from a safe place to the local machine and name it `ca.key`. + +3. Download latest `smp-server` binary [from Github releases](https://github.com/simplex-chat/simplexmq/releases): + + ```sh + curl -L 'https://github.com/simplex-chat/simplexmq/releases/latest/download/smp-server-ubuntu-20_04-x86-64' -o smp-server + ``` + +4. Put the `smp-server` binary to your `$PATH` and make it executable: + + ```sh + sudo mv smp-server /usr/local/bin/ && chmod +x /usr/local/bin/smp-server + ``` + +5. Export a variable to configure your path to smp-server configuration: + + ```sh + export SMP_SERVER_CFG_PATH=$HOME/simplex/smp/config + ``` + +6. Execute the following command: + + ```sh + smp-server cert + ``` + + This command should print: + + ```sh + Certificate request self-signature ok + subject=CN = + Generated new server credentials + ---------- + You should store CA private key securely and delete it from the server. + If server TLS credential is compromised this key can be used to sign a new one, keeping the same server identity and established connections. + CA private key location: + $HOME/simplex/smp/config/ca.key + ---------- + ``` + +7. Remove the CA key from the config folder (make sure you have a backup!): + + ```sh + rm $HOME/simplex/smp/config/ca.key + ``` + +8. Upload new certificates to the server: + + ```sh + rsync -hzasP $HOME/simplex/smp/config/ @:/etc/opt/simplex/ + ``` + +9. Connect to the server via SSH and restart the service: + + ```sh + ssh @ "systemctl restart smp-server" + ``` + +10. Done! + +## Tor: installation and configuration + +### Installation for onion address + +SMP-server can also be deployed to be available via [Tor](https://www.torproject.org) network. Run the following commands as `root` user. + +1. Install tor: + + We're assuming you're using Ubuntu/Debian based distributions. If not, please refer to [offical tor documentation](https://community.torproject.org/onion-services/setup/install/) or your distribution guide. + + - Configure offical Tor PPA repository: + + ```sh + CODENAME="$(lsb_release -c | awk '{print $2}')" + echo "deb [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org ${CODENAME} main + deb-src [signed-by=/usr/share/keyrings/tor-archive-keyring.gpg] https://deb.torproject.org/torproject.org ${CODENAME} main" > /etc/apt/sources.list.d/tor.list + ``` + + - Import repository key: + + ```sh + curl --proto '=https' --tlsv1.2 -sSf https://deb.torproject.org/torproject.org/A3C4F0F979CAA22CDBA8F512EE8CBC9E886DDD89.asc | gpg --dearmor | tee /usr/share/keyrings/tor-archive-keyring.gpg >/dev/null + ``` + + - Update repository index: + + ```sh + apt update + ``` + + - Install `tor` package: + + ```sh + apt install -y tor deb.torproject.org-keyring + ``` + +2. Configure tor: + + - File configuration: + + Open tor configuration with your editor of choice (`nano`,`vim`,`emacs`,etc.): + + ```sh + vim /etc/tor/torrc + ``` + + And insert the following lines to the bottom of configuration. Please note lines starting with `#`: this is comments about each individual options. + + ```sh + # Enable log (otherwise, tor doesn't seem to deploy onion address) + Log notice file /var/log/tor/notices.log + # Enable single hop routing (2 options below are dependencies of the third) - It will reduce the latency at the cost of lower anonimity of the server - as SMP-server onion address is used in the clients together with public address, this is ok. If you deploy SMP-server with onion-only address, you may want to keep standard configuration instead. + SOCKSPort 0 + HiddenServiceNonAnonymousMode 1 + HiddenServiceSingleHopMode 1 + # smp-server hidden service host directory and port mappings + HiddenServiceDir /var/lib/tor/simplex-smp/ + HiddenServicePort 5223 localhost:5223 + ``` + + - Create directories: + + ```sh + mkdir /var/lib/tor/simplex-smp/ && chown debian-tor:debian-tor /var/lib/tor/simplex-smp/ && chmod 700 /var/lib/tor/simplex-smp/ + ``` + +3. Start tor: + + Enable `systemd` service and start tor. Offical `tor` is a bit flaky on the first start and may not create onion host address, so we're restarting it just in case. + + ```sh + systemctl enable --now tor && systemctl restart tor + ``` + +4. Display onion host: + + Execute the following command to display your onion host address: + + ```sh + cat /var/lib/tor/simplex-smp/hostname + ``` + +### SOCKS port for SMP PROXY + +SMP-server versions starting from `v5.8.0-beta.0` can be configured to PROXY smp servers available exclusively through [Tor](https://www.torproject.org) network to be accessible to the clients that do not use Tor. Run the following commands as `root` user. + +1. Install tor as described in the [previous section](#installation-for-onion-address). + +2. Execute the following command to creatae a new Tor daemon instance: + + ```sh + tor-instance-create tor2 + ``` + +3. Open the `tor2` configuration and replace its content with the following lines: + + ```sh + vim /etc/tor/instances/tor2/torrc + ``` + + ```sh + # Log tor to systemd daemon + Log notice syslog + # Listen to local 9050 port for socks proxy + SocksPort 9050 + ``` + +3. Enable service at startup and start the daemon: + + ```sh + systemctl enable --now tor@tor2 + ``` + + You can check `tor2` logs with the following command: + + ```sh + journalctl -u tor@tor2 + ``` + +4. After [server initialization](#configuration), configure the `PROXY` section like so: + + ```ini + ... + [PROXY] + socks_proxy: 127.0.0.1:9050 + own_server_domains: + ... + ``` ## Documentation @@ -417,7 +681,7 @@ 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 SMP server +## Updating your SMP server To update your smp-server to latest version, choose your installation method and follow the steps: @@ -474,7 +738,7 @@ To update your smp-server to latest version, choose your installation method and docker image prune ``` -### Configuring the app to use the server +## Configuring the app to use the server To configure the app to use your messaging server copy it's full address, including password, and add it to the app. You have an option to use your server together with preset servers or without them - you can remove or disable them. diff --git a/package.yaml b/package.yaml index 64a6c15894..2e73355d6f 100644 --- a/package.yaml +++ b/package.yaml @@ -1,5 +1,5 @@ name: simplex-chat -version: 5.8.0.1 +version: 5.8.0.5 #synopsis: #description: homepage: https://github.com/simplex-chat/simplex-chat#readme @@ -152,12 +152,31 @@ tests: ghc-options: # - -haddock - -O2 - - -Wall + - -Weverything + - -Wno-missing-exported-signatures + - -Wno-missing-import-lists + - -Wno-missed-specialisations + - -Wno-all-missed-specialisations + - -Wno-unsafe + - -Wno-safe + - -Wno-missing-local-signatures + - -Wno-missing-kind-signatures + - -Wno-missing-deriving-strategies + - -Wno-monomorphism-restriction + - -Wno-prepositive-qualified-module + - -Wno-unused-packages + - -Wno-implicit-prelude + - -Wno-missing-safe-haskell-mode + - -Wno-missing-export-lists + - -Wno-partial-fields - -Wcompat + - -Werror=incomplete-record-updates - -Werror=incomplete-patterns + - -Werror=missing-methods + - -Werror=incomplete-uni-patterns + - -Werror=tabs - -Wredundant-constraints - -Wincomplete-record-updates - - -Wincomplete-uni-patterns - -Wunused-type-patterns default-extensions: diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix index 512638d454..6b74da3798 100644 --- a/scripts/nix/sha256map.nix +++ b/scripts/nix/sha256map.nix @@ -1,5 +1,5 @@ { - "https://github.com/simplex-chat/simplexmq.git"."2e5433676eaa5de93ed1ea9726706b9633308477" = "0ichdf5vsdizqxqy8amx3f5grx5sghiv2gajd2w3l73vnr2rv3bd"; + "https://github.com/simplex-chat/simplexmq.git"."2e4f507919dd3a08e5c4106fde193fee4414de3c" = "1s035v77rpk8xs8gcfqm2ddp1mf5n35zrkq68gxqr634r2r9vbaj"; "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 2b0a55bb68..5c4e9cd53f 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.8.0.1 +version: 5.8.0.5 category: Web, System, Services, Cryptography homepage: https://github.com/simplex-chat/simplex-chat#readme author: simplex.chat @@ -145,6 +145,7 @@ library Simplex.Chat.Migrations.M20240501_chat_deleted Simplex.Chat.Migrations.M20240510_chat_items_via_proxy Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays + Simplex.Chat.Migrations.M20240528_quota_err_counter Simplex.Chat.Mobile Simplex.Chat.Mobile.File Simplex.Chat.Mobile.Shared @@ -190,7 +191,7 @@ library src default-extensions: StrictData - ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns + ghc-options: -O2 -Weverything -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missed-specialisations -Wno-all-missed-specialisations -Wno-unsafe -Wno-safe -Wno-missing-local-signatures -Wno-missing-kind-signatures -Wno-missing-deriving-strategies -Wno-monomorphism-restriction -Wno-prepositive-qualified-module -Wno-unused-packages -Wno-implicit-prelude -Wno-missing-safe-haskell-mode -Wno-missing-export-lists -Wno-partial-fields -Wcompat -Werror=incomplete-record-updates -Werror=incomplete-patterns -Werror=missing-methods -Werror=incomplete-uni-patterns -Werror=tabs -Wredundant-constraints -Wincomplete-record-updates -Wunused-type-patterns build-depends: aeson ==2.2.* , ansi-terminal >=0.10 && <0.12 @@ -252,7 +253,7 @@ executable simplex-bot apps/simplex-bot default-extensions: StrictData - ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded + ghc-options: -O2 -Weverything -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missed-specialisations -Wno-all-missed-specialisations -Wno-unsafe -Wno-safe -Wno-missing-local-signatures -Wno-missing-kind-signatures -Wno-missing-deriving-strategies -Wno-monomorphism-restriction -Wno-prepositive-qualified-module -Wno-unused-packages -Wno-implicit-prelude -Wno-missing-safe-haskell-mode -Wno-missing-export-lists -Wno-partial-fields -Wcompat -Werror=incomplete-record-updates -Werror=incomplete-patterns -Werror=missing-methods -Werror=incomplete-uni-patterns -Werror=tabs -Wredundant-constraints -Wincomplete-record-updates -Wunused-type-patterns -threaded build-depends: aeson ==2.2.* , ansi-terminal >=0.10 && <0.12 @@ -315,7 +316,7 @@ executable simplex-bot-advanced apps/simplex-bot-advanced default-extensions: StrictData - ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded + ghc-options: -O2 -Weverything -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missed-specialisations -Wno-all-missed-specialisations -Wno-unsafe -Wno-safe -Wno-missing-local-signatures -Wno-missing-kind-signatures -Wno-missing-deriving-strategies -Wno-monomorphism-restriction -Wno-prepositive-qualified-module -Wno-unused-packages -Wno-implicit-prelude -Wno-missing-safe-haskell-mode -Wno-missing-export-lists -Wno-partial-fields -Wcompat -Werror=incomplete-record-updates -Werror=incomplete-patterns -Werror=missing-methods -Werror=incomplete-uni-patterns -Werror=tabs -Wredundant-constraints -Wincomplete-record-updates -Wunused-type-patterns -threaded build-depends: aeson ==2.2.* , ansi-terminal >=0.10 && <0.12 @@ -381,7 +382,7 @@ executable simplex-broadcast-bot Broadcast.Bot Broadcast.Options Paths_simplex_chat - ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded + ghc-options: -O2 -Weverything -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missed-specialisations -Wno-all-missed-specialisations -Wno-unsafe -Wno-safe -Wno-missing-local-signatures -Wno-missing-kind-signatures -Wno-missing-deriving-strategies -Wno-monomorphism-restriction -Wno-prepositive-qualified-module -Wno-unused-packages -Wno-implicit-prelude -Wno-missing-safe-haskell-mode -Wno-missing-export-lists -Wno-partial-fields -Wcompat -Werror=incomplete-record-updates -Werror=incomplete-patterns -Werror=missing-methods -Werror=incomplete-uni-patterns -Werror=tabs -Wredundant-constraints -Wincomplete-record-updates -Wunused-type-patterns -threaded build-depends: aeson ==2.2.* , ansi-terminal >=0.10 && <0.12 @@ -445,7 +446,7 @@ executable simplex-chat apps/simplex-chat default-extensions: StrictData - ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded + ghc-options: -O2 -Weverything -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missed-specialisations -Wno-all-missed-specialisations -Wno-unsafe -Wno-safe -Wno-missing-local-signatures -Wno-missing-kind-signatures -Wno-missing-deriving-strategies -Wno-monomorphism-restriction -Wno-prepositive-qualified-module -Wno-unused-packages -Wno-implicit-prelude -Wno-missing-safe-haskell-mode -Wno-missing-export-lists -Wno-partial-fields -Wcompat -Werror=incomplete-record-updates -Werror=incomplete-patterns -Werror=missing-methods -Werror=incomplete-uni-patterns -Werror=tabs -Wredundant-constraints -Wincomplete-record-updates -Wunused-type-patterns -threaded build-depends: aeson ==2.2.* , ansi-terminal >=0.10 && <0.12 @@ -515,7 +516,7 @@ executable simplex-directory-service Directory.Service Directory.Store Paths_simplex_chat - ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded + ghc-options: -O2 -Weverything -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missed-specialisations -Wno-all-missed-specialisations -Wno-unsafe -Wno-safe -Wno-missing-local-signatures -Wno-missing-kind-signatures -Wno-missing-deriving-strategies -Wno-monomorphism-restriction -Wno-prepositive-qualified-module -Wno-unused-packages -Wno-implicit-prelude -Wno-missing-safe-haskell-mode -Wno-missing-export-lists -Wno-partial-fields -Wcompat -Werror=incomplete-record-updates -Werror=incomplete-patterns -Werror=missing-methods -Werror=incomplete-uni-patterns -Werror=tabs -Wredundant-constraints -Wincomplete-record-updates -Wunused-type-patterns -threaded build-depends: aeson ==2.2.* , ansi-terminal >=0.10 && <0.12 @@ -610,7 +611,7 @@ test-suite simplex-chat-test apps/simplex-directory-service/src default-extensions: StrictData - ghc-options: -O2 -Wall -Wcompat -Werror=incomplete-patterns -Wredundant-constraints -Wincomplete-record-updates -Wincomplete-uni-patterns -Wunused-type-patterns -threaded + ghc-options: -O2 -Weverything -Wno-missing-exported-signatures -Wno-missing-import-lists -Wno-missed-specialisations -Wno-all-missed-specialisations -Wno-unsafe -Wno-safe -Wno-missing-local-signatures -Wno-missing-kind-signatures -Wno-missing-deriving-strategies -Wno-monomorphism-restriction -Wno-prepositive-qualified-module -Wno-unused-packages -Wno-implicit-prelude -Wno-missing-safe-haskell-mode -Wno-missing-export-lists -Wno-partial-fields -Wcompat -Werror=incomplete-record-updates -Werror=incomplete-patterns -Werror=missing-methods -Werror=incomplete-uni-patterns -Werror=tabs -Wredundant-constraints -Wincomplete-record-updates -Wunused-type-patterns -threaded build-depends: QuickCheck ==2.14.* , aeson ==2.2.* diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 743cef932c..5eb75148cc 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -1,3 +1,4 @@ +{-# LANGUAGE BangPatterns #-} {-# LANGUAGE DataKinds #-} {-# LANGUAGE DuplicateRecordFields #-} {-# LANGUAGE FlexibleContexts #-} @@ -93,7 +94,7 @@ import qualified Simplex.FileTransfer.Description as FD import Simplex.FileTransfer.Protocol (FileParty (..), FilePartyI) import qualified Simplex.FileTransfer.Transport as XFTP import Simplex.Messaging.Agent as Agent -import Simplex.Messaging.Agent.Client (AgentStatsKey (..), SubInfo (..), agentClientStore, getAgentWorkersDetails, getAgentWorkersSummary, ipAddressProtected, temporaryAgentError, withLockMap) +import Simplex.Messaging.Agent.Client (AgentStatsKey (..), SubInfo (..), agentClientStore, getAgentQueuesInfo, getAgentWorkersDetails, getAgentWorkersSummary, getNetworkConfig', ipAddressProtected, temporaryAgentError, withLockMap) import Simplex.Messaging.Agent.Env.SQLite (AgentConfig (..), InitialAgentServers (..), createAgentStore, defaultAgentConfig) import Simplex.Messaging.Agent.Lock (withLock) import Simplex.Messaging.Agent.Protocol @@ -102,7 +103,7 @@ import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), Migrati import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB import qualified Simplex.Messaging.Agent.Store.SQLite.Migrations as Migrations -import Simplex.Messaging.Client (ProxyClientError (..), defaultNetworkConfig) +import Simplex.Messaging.Client (ProxyClientError (..), NetworkConfig (..), defaultNetworkConfig) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.File as CF @@ -217,7 +218,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, optTempDirectory, showReactions, allowInstantFiles, autoAcceptFileSize} + ChatOpts {coreOptions = CoreChatOpts {smpServers, xftpServers, simpleNetCfg, 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} @@ -228,6 +229,7 @@ newChatController smpAgent <- getSMPAgentClient aCfg {tbqSize} servers agentStore backgroundMode agentAsync <- newTVarIO Nothing random <- liftIO C.newRandom + eventSeq <- newTVarIO 0 inputQ <- newTBQueueIO tbqSize outputQ <- newTBQueueIO tbqSize connNetworkStatuses <- atomically TM.empty @@ -265,6 +267,7 @@ newChatController chatStore, chatStoreChanged, random, + eventSeq, inputQ, outputQ, connNetworkStatuses, @@ -300,7 +303,7 @@ newChatController let DefaultAgentServers {smp = defSmp, xftp = defXftp} = defaultServers smp' = fromMaybe defSmp (nonEmpty smpServers) xftp' = fromMaybe defXftp (nonEmpty xftpServers) - in defaultServers {smp = smp', xftp = xftp', netCfg = networkConfig} + in defaultServers {smp = smp', xftp = xftp', netCfg = updateNetworkConfig defaultNetworkConfig simpleNetCfg} agentServers :: ChatConfig -> IO InitialAgentServers agentServers config@ChatConfig {defaultServers = defServers@DefaultAgentServers {ntf, netCfg}} = do users <- withTransaction chatStore getUsers @@ -318,6 +321,13 @@ newChatController userServers :: User -> IO (NonEmpty (ProtoServerWithAuth p)) userServers user' = activeAgentServers config protocol <$> withTransaction chatStore (`getProtocolServers` user') +updateNetworkConfig :: NetworkConfig -> SimpleNetCfg -> NetworkConfig +updateNetworkConfig cfg SimpleNetCfg {socksProxy, smpProxyMode_, smpProxyFallback_, tcpTimeout_, logTLSErrors} = + let cfg1 = maybe cfg (\smpProxyMode -> cfg {smpProxyMode}) smpProxyMode_ + cfg2 = maybe cfg1 (\smpProxyFallback -> cfg1 {smpProxyFallback}) smpProxyFallback_ + cfg3 = maybe cfg2 (\tcpTimeout -> cfg2 {tcpTimeout, tcpConnectTimeout = (tcpTimeout * 3) `div` 2}) tcpTimeout_ + in cfg3 {socksProxy, logTLSErrors} + withChatLock :: String -> CM a -> CM a withChatLock name action = asks chatLock >>= \l -> withLock l name action @@ -1339,7 +1349,11 @@ processChatCommand' vr = \case processChatCommand $ APIGetChatItemTTL userId APISetNetworkConfig cfg -> withUser' $ \_ -> lift (withAgent' (`setNetworkConfig` cfg)) >> ok_ APIGetNetworkConfig -> withUser' $ \_ -> - lift $ CRNetworkConfig <$> withAgent' getNetworkConfig + CRNetworkConfig <$> lift getNetworkConfig + SetNetworkConfig netCfg -> do + cfg <- lift getNetworkConfig + void . processChatCommand $ APISetNetworkConfig $ updateNetworkConfig cfg netCfg + pure $ CRNetworkConfig cfg APISetNetworkInfo info -> lift (withAgent' (`setUserNetworkInfo` info)) >> ok_ ReconnectAllServers -> withUser' $ \_ -> lift (withAgent' reconnectAllServers) >> ok_ APISetChatSettings (ChatRef cType chatId) chatSettings -> withUser $ \user -> case cType of @@ -1376,6 +1390,11 @@ processChatCommand' vr = \case forM customUserProfileId $ \profileId -> withStore (\db -> getProfileById db userId profileId) connectionStats <- mapM (withAgent . flip getConnectionServers) (contactConnId ct) pure $ CRContactInfo user ct connectionStats (fmap fromLocalProfile incognitoProfile) + APIContactQueueInfo contactId -> withUser $ \user -> do + ct@Contact {activeConn} <- withStore $ \db -> getContact db vr user contactId + case activeConn of + Just conn -> getConnQueueInfo user conn + Nothing -> throwChatError $ CEContactNotActive ct APIGroupInfo gId -> withUser $ \user -> do (g, s) <- withStore $ \db -> (,) <$> getGroupInfo db vr user gId <*> liftIO (getGroupSummary db user gId) pure $ CRGroupInfo user g s @@ -1383,6 +1402,11 @@ processChatCommand' vr = \case (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 + APIGroupMemberQueueInfo gId gMemberId -> withUser $ \user -> do + GroupMember {activeConn} <- withStore $ \db -> getGroupMember db vr user gId gMemberId + case activeConn of + Just conn -> getConnQueueInfo user conn + Nothing -> throwChatError CEGroupMemberNotActive APISwitchContact contactId -> withUser $ \user -> do ct <- withStore $ \db -> getContact db vr user contactId case contactConnId ct of @@ -1469,14 +1493,14 @@ processChatCommand' vr = \case ct@Contact {activeConn} <- withStore $ \db -> getContact db vr user contactId case activeConn of Just conn -> do - withStore' $ \db -> setConnectionAuthErrCounter db user conn 0 + withStore' $ \db -> setAuthErrCounter db user conn 0 ok user Nothing -> throwChatError $ CEContactNotActive ct APIEnableGroupMember gId gMemberId -> withUser $ \user -> do GroupMember {activeConn} <- withStore $ \db -> getGroupMember db vr user gId gMemberId case activeConn of Just conn -> do - withStore' $ \db -> setConnectionAuthErrCounter db user conn 0 + withStore' $ \db -> setAuthErrCounter db user conn 0 ok user _ -> throwChatError CEGroupMemberNotActive SetShowMessages cName ntfOn -> updateChatSettings cName (\cs -> cs {enableNtfs = ntfOn}) @@ -1494,6 +1518,8 @@ processChatCommand' vr = \case groupId <- withStore $ \db -> getGroupIdByName db user gName processChatCommand $ APIGroupInfo groupId GroupMemberInfo gName mName -> withMemberName gName mName APIGroupMemberInfo + ContactQueueInfo cName -> withContactName cName APIContactQueueInfo + GroupMemberQueueInfo gName mName -> withMemberName gName mName APIGroupMemberQueueInfo SwitchContact cName -> withContactName cName APISwitchContact SwitchGroupMember gName mName -> withMemberName gName mName APISwitchGroupMember AbortSwitchContact cName -> withContactName cName APIAbortSwitchContact @@ -1547,8 +1573,8 @@ processChatCommand' vr = \case let chatV = agentToChatVersion agentV dm <- encodeConnInfoPQ pqSup' chatV $ XInfo profileToSend connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup' - conn <- withStore' $ \db -> createDirectConnection db user connId cReq ConnJoined (incognitoProfile $> profileToSend) subMode chatV pqSup' - void . withAgent $ \a -> joinConnection a (aUserId user) (Just connId) True cReq dm pqSup' subMode + conn@PendingContactConnection {pccConnId} <- withStore' $ \db -> createDirectConnection db user connId cReq ConnJoined (incognitoProfile $> profileToSend) subMode chatV pqSup' + joinPreparedAgentConnection user pccConnId connId cReq dm pqSup' subMode pure $ CRSentConfirmation user conn APIConnect userId incognito (Just (ACR SCMContact cReq)) -> withUserId userId $ \user -> connectViaContact user incognito cReq APIConnect _ _ Nothing -> throwChatError CEInvalidConnReq @@ -1701,7 +1727,7 @@ processChatCommand' vr = \case sndMsgs <- lift $ createSndMessages idsEvts let msgReqs_ :: NonEmpty (Either ChatError MsgReq) = L.zipWith (fmap . ctMsgReq) ctConns sndMsgs (errs, ctSndMsgs :: [(Contact, SndMessage)]) <- - lift $ partitionEithers . L.toList . zipWith3' combineResults ctConns sndMsgs <$> deliverMessagesB msgReqs_ + partitionEithers . L.toList . zipWith3' combineResults ctConns sndMsgs <$> deliverMessagesB msgReqs_ timestamp <- liftIO getCurrentTime lift . void $ withStoreBatch' $ \db -> map (createCI db user timestamp) ctSndMsgs pure CRBroadcastSent {user, msgContent = mc, successes = length ctSndMsgs, failures = length errs, timestamp} @@ -1803,12 +1829,20 @@ processChatCommand' vr = \case dm <- encodeConnInfo $ XGrpAcpt membershipMemId agentConnId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True connRequest PQSupportOff let chatV = vr `peerConnChatVersion` peerChatVRange - withStore' $ \db -> do - createMemberConnection db userId fromMember agentConnId chatV peerChatVRange subMode + cId <- withStore' $ \db -> do + Connection {connId = cId} <- createMemberConnection db userId fromMember agentConnId chatV peerChatVRange subMode updateGroupMemberStatus db userId fromMember GSMemAccepted updateGroupMemberStatus db userId membership GSMemAccepted - void . withAgent $ \a -> joinConnection a (aUserId user) (Just agentConnId) True connRequest dm PQSupportOff subMode - updateCIGroupInvitationStatus user g CIGISAccepted `catchChatError` \_ -> pure () + pure cId + void (withAgent $ \a -> joinConnection a (aUserId user) (Just agentConnId) True connRequest dm PQSupportOff subMode) + `catchChatError` \e -> do + withStore' $ \db -> do + deleteConnectionRecord db user cId + updateGroupMemberStatus db userId fromMember GSMemInvited + updateGroupMemberStatus db userId membership GSMemInvited + withAgent $ \a -> deleteConnectionAsync a False agentConnId + throwError e + updateCIGroupInvitationStatus user g CIGISAccepted `catchChatError` (toView . CRChatError (Just user)) pure $ CRUserAcceptedGroupSent user g {membership = membership {memberStatus = GSMemAccepted}} Nothing Nothing -> throwChatError $ CEContactNotActive ct APIMemberRole groupId memberId memRole -> withUser $ \user -> do @@ -2236,6 +2270,7 @@ processChatCommand' vr = \case SubInfo {server, subError = Just e} -> M.alter (Just . maybe [e] (e :)) server m _ -> m GetAgentSubsDetails -> lift $ CRAgentSubsDetails <$> withAgent' getAgentSubscriptions + GetAgentQueuesInfo -> lift $ CRAgentQueuesInfo <$> withAgent' getAgentQueuesInfo -- CustomChatCommand is unsupported, it can be processed in preCmdHook -- in a modified CLI app or core - the hook should return Either ChatResponse ChatCommand CustomChatCommand _cmd -> withUser $ \user -> pure $ chatCmdError (Just user) "not supported" @@ -2335,8 +2370,8 @@ processChatCommand' vr = \case -- [incognito] generate profile to send incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing subMode <- chatReadVar subscriptionMode - conn <- withStore' $ \db -> createConnReqConnection db userId connId cReqHash xContactId incognitoProfile groupLinkId subMode chatV pqSup - joinContact user connId cReq incognitoProfile xContactId inGroup pqSup chatV + conn@PendingContactConnection {pccConnId} <- withStore' $ \db -> createConnReqConnection db userId connId cReqHash xContactId incognitoProfile groupLinkId subMode chatV pqSup + joinContact user pccConnId connId cReq incognitoProfile xContactId inGroup pqSup chatV pure $ CRSentInvitation user conn incognitoProfile connectContactViaAddress :: User -> IncognitoEnabled -> Contact -> ConnectionRequestUri 'CMContact -> CM ChatResponse connectContactViaAddress user incognito ct cReq = @@ -2348,8 +2383,8 @@ processChatCommand' vr = \case -- [incognito] generate profile to send incognitoProfile <- if incognito then Just <$> liftIO generateRandomProfile else pure Nothing subMode <- chatReadVar subscriptionMode - ct' <- withStore $ \db -> createAddressContactConnection db vr user ct connId cReqHash newXContactId incognitoProfile subMode chatV pqSup - joinContact user connId cReq incognitoProfile newXContactId False pqSup chatV + (pccConnId, ct') <- withStore $ \db -> createAddressContactConnection db vr user ct connId cReqHash newXContactId incognitoProfile subMode chatV pqSup + joinContact user pccConnId connId cReq incognitoProfile newXContactId False pqSup chatV pure $ CRSentInvitationToContact user ct' incognitoProfile prepareContact :: User -> ConnectionRequestUri 'CMContact -> PQSupport -> CM (ConnId, VersionChat) prepareContact user cReq pqSup = do @@ -2362,12 +2397,19 @@ processChatCommand' vr = \case let chatV = agentToChatVersion agentV connId <- withAgent $ \a -> prepareConnectionToJoin a (aUserId user) True cReq pqSup pure (connId, chatV) - joinContact :: User -> ConnId -> ConnectionRequestUri 'CMContact -> Maybe Profile -> XContactId -> Bool -> PQSupport -> VersionChat -> CM () - joinContact user connId cReq incognitoProfile xContactId inGroup pqSup chatV = do + joinContact :: User -> Int64 -> ConnId -> ConnectionRequestUri 'CMContact -> Maybe Profile -> XContactId -> Bool -> PQSupport -> VersionChat -> CM () + joinContact user pccConnId connId cReq incognitoProfile xContactId inGroup pqSup chatV = do let profileToSend = userProfileToSend user incognitoProfile Nothing inGroup dm <- encodeConnInfoPQ pqSup chatV (XContact profileToSend $ Just xContactId) subMode <- chatReadVar subscriptionMode - void . withAgent $ \a -> joinConnection a (aUserId user) (Just connId) True cReq dm pqSup subMode + joinPreparedAgentConnection user pccConnId connId cReq dm pqSup subMode + joinPreparedAgentConnection :: User -> Int64 -> ConnId -> ConnectionRequestUri m -> ByteString -> PQSupport -> SubscriptionMode -> CM () + joinPreparedAgentConnection user pccConnId connId cReq connInfo pqSup subMode = do + void (withAgent $ \a -> joinConnection a (aUserId user) (Just connId) True cReq connInfo pqSup subMode) + `catchChatError` \e -> do + withStore' $ \db -> deleteConnectionRecord db user pccConnId + withAgent $ \a -> deleteConnectionAsync a False connId + throwError e contactMember :: Contact -> [GroupMember] -> Maybe GroupMember contactMember Contact {contactId} = find $ \GroupMember {memberContactId = cId, memberStatus = s} -> @@ -2397,7 +2439,7 @@ processChatCommand' vr = \case Just changedCts -> do let idsEvts = L.map ctSndEvent changedCts msgReqs_ <- lift $ L.zipWith ctMsgReq changedCts <$> createSndMessages idsEvts - (errs, cts) <- lift $ partitionEithers . L.toList . L.zipWith (second . const) changedCts <$> deliverMessagesB msgReqs_ + (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 lift $ createContactsSndFeatureItems user' changedCts' @@ -2776,6 +2818,9 @@ processChatCommand' vr = \case pure CIFile {fileId, fileName = takeFileName filePath, fileSize, fileSource = Just cf, fileStatus = CIFSSndStored, fileProtocol = FPLocal} let ci = mkChatItem cd ciId content ciFile_ Nothing Nothing itemForwarded Nothing False createdAt Nothing createdAt pure . CRNewChatItem user $ AChatItem SCTLocal SMDSnd (LocalChat nf) ci + getConnQueueInfo user Connection {connId, agentConnId = AgentConnId acId} = do + msgInfo <- withStore' (`getLastRcvMsgInfo` connId) + CRQueueInfo user msgInfo <$> withAgent (`getConnectionQueueInfo` acId) contactCITimed :: Contact -> CM (Maybe CITimed) contactCITimed ct = sndContactCITimed False ct Nothing @@ -3160,7 +3205,7 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} pure $ filter (`notElem` knownSrvs) srvs ipProtectedForSrvs :: [XFTPServer] -> CM Bool ipProtectedForSrvs srvs = do - netCfg <- lift $ withAgent' getNetworkConfig + netCfg <- lift getNetworkConfig pure $ all (ipAddressProtected netCfg) srvs relaysNotApproved :: [XFTPServer] -> CM () relaysNotApproved unknownSrvs = do @@ -3168,6 +3213,9 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} forM_ aci_ $ \aci -> toView $ CRChatItemUpdated user aci throwChatError $ CEFileNotApproved fileId unknownSrvs +getNetworkConfig :: CM' NetworkConfig +getNetworkConfig = withAgent' $ liftIO . getNetworkConfig' + resetRcvCIFileStatus :: User -> FileTransferId -> CIFileStatus 'MDRcv -> CM (Maybe AChatItem) resetRcvCIFileStatus user fileId ciFileStatus = do vr <- chatVersionRange @@ -3316,7 +3364,10 @@ deleteGroupLink_ user gInfo conn = do agentSubscriber :: CM' () agentSubscriber = do q <- asks $ subQ . smpAgent - forever $ atomically (readTBQueue q) >>= process + forever (atomically (readTBQueue q) >>= process) + `E.catchAny` \e -> do + toView' $ CRChatError Nothing $ ChatErrorAgent (CRITICAL True $ "Message reception stopped: " <> show e) Nothing + E.throwIO e where process :: (ACorrId, EntityId, APartyCmd 'Agent) -> CM' () process (corrId, entId, APC e msg) = run $ case e of @@ -3936,17 +3987,22 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = -- 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 meta $ + withAckMessage' "new contact msg" agentConnId meta $ void $ saveDirectRcvMSG conn meta msgBody - SENT msgId _proxy -> + SENT msgId _proxy -> do + void $ continueSending connEntity conn sentMsgDeliveryEvent conn msgId OK -> -- [async agent commands] continuation on receiving OK when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () + QCONT -> + void $ continueSending connEntity conn + MWARN _ err -> + processConnMWARN connEntity conn err MERR _ err -> do toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) - incAuthErrCounter connEntity conn err + processConnMERR connEntity conn err MERRS _ err -> do -- error cannot be AUTH error here toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) @@ -3967,14 +4023,18 @@ 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 -> - withAckMessage agentConnId msgMeta True $ do + MSG msgMeta _msgFlags msgBody -> do + tags <- newTVarIO [] + withAckMessage "contact msg" agentConnId msgMeta True (Just tags) $ \eInfo -> 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 msgBody + let tag = toCMEventTag event + atomically $ writeTVar tags [tshow tag] + logInfo $ "contact msg=" <> tshow tag <> " " <> eInfo let ct'' = ct' {activeConn = Just conn''} :: Contact - assertDirectAllowed user MDRcv ct'' $ toCMEventTag event + assertDirectAllowed user MDRcv ct'' tag case event of XMsgNew mc -> newContentMessage ct'' mc msg msgMeta XMsgFileDescr sharedMsgId fileDescr -> messageFileDescription ct'' sharedMsgId fileDescr @@ -3999,9 +4059,9 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = BFileChunk sharedMsgId chunk -> bFileChunk ct'' sharedMsgId chunk msgMeta _ -> messageError $ "unsupported message: " <> T.pack (show event) let Contact {chatSettings = ChatSettings {sendRcpts}} = ct'' - pure $ fromMaybe (sendRcptsContacts user) sendRcpts && hasDeliveryReceipt (toCMEventTag event) + pure $ fromMaybe (sendRcptsContacts user) sendRcpts && hasDeliveryReceipt tag RCVD msgMeta msgRcpt -> - withAckMessage' agentConnId msgMeta $ + withAckMessage' "contact rcvd" agentConnId msgMeta $ directMsgReceived ct conn msgMeta msgRcpt CONF confId pqSupport _ connInfo -> do conn' <- processCONFpqSupport conn pqSupport @@ -4073,6 +4133,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = let connectedIncognito = contactConnIncognito ct || incognitoMembership gInfo when (memberCategory m == GCPreMember) $ probeMatchingContactsAndMembers ct connectedIncognito True SENT msgId proxy -> do + void $ continueSending connEntity conn sentMsgDeliveryEvent conn msgId checkSndInlineFTComplete conn msgId ci_ <- withStore $ \db -> do @@ -4113,12 +4174,15 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = OK -> -- [async agent commands] continuation on receiving OK when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () - MWARN msgId err -> + QCONT -> + void $ continueSending connEntity conn + MWARN msgId err -> do updateDirectItemStatus ct conn msgId (CISSndWarning $ agentSndError err) + processConnMWARN connEntity conn err MERR msgId err -> do updateDirectItemStatus ct conn msgId (CISSndError $ agentSndError err) toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) - incAuthErrCounter connEntity conn err + processConnMERR connEntity conn err MERRS msgIds err -> do -- error cannot be AUTH error here updateDirectItemsStatus ct conn (L.toList msgIds) (CISSndError $ agentSndError err) @@ -4380,19 +4444,26 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = void $ sendDirectMemberMessage imConn (XGrpMemCon memberId) groupId _ -> messageWarning "sendXGrpMemCon: member category GCPreMember or GCPostMember is expected" MSG msgMeta _msgFlags msgBody -> do - withAckMessage agentConnId msgMeta True $ do + tags <- newTVarIO [] + withAckMessage "group msg" agentConnId msgMeta True (Just tags) $ \eInfo -> do checkIntegrityCreateItem (CDGroupRcv gInfo m) msgMeta `catchChatError` \_ -> pure () forM_ aChatMsgs $ \case Right (ACMsg _ chatMsg) -> - processEvent chatMsg `catchChatError` \e -> toView $ CRChatError (Just user) e - Left e -> toView $ CRChatError (Just user) (ChatError . CEException $ "error parsing chat message: " <> e) - forwardMsg_ `catchChatError` \_ -> pure () + processEvent tags eInfo chatMsg `catchChatError` \e -> toView $ CRChatError (Just user) e + Left e -> do + atomically $ modifyTVar' tags ("error" :) + logInfo $ "group msg=error " <> eInfo <> " " <> tshow e + toView $ CRChatError (Just user) (ChatError . CEException $ "error parsing chat message: " <> e) + forwardMsg_ `catchChatError` (toView . CRChatError (Just user)) checkSendRcpt $ rights aChatMsgs where aChatMsgs = parseChatMessages msgBody brokerTs = metaBrokerTs msgMeta - processEvent :: MsgEncodingI e => ChatMessage e -> CM () - processEvent chatMsg = do + processEvent :: TVar [Text] -> Text -> MsgEncodingI e => ChatMessage e -> CM () + processEvent tags eInfo chatMsg@ChatMessage {chatMsgEvent} = do + let tag = toCMEventTag chatMsgEvent + atomically $ modifyTVar' tags (tshow tag :) + logInfo $ "group msg=" <> tshow tag <> " " <> eInfo (m', conn', msg@RcvMessage {chatMsgEvent = ACME _ event}) <- saveGroupRcvMsg user groupId m conn msgMeta msgBody chatMsg case event of XMsgNew mc -> memberCanSend m' $ newGroupContentMessage gInfo m' mc msg brokerTs False @@ -4423,7 +4494,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = XInfoProbeCheck probeHash -> xInfoProbeCheck (COMGroupMember m') probeHash XInfoProbeOk probe -> xInfoProbeOk (COMGroupMember m') probe BFileChunk sharedMsgId chunk -> bFileChunkGroup gInfo sharedMsgId chunk msgMeta - _ -> messageError $ "unsupported message: " <> T.pack (show event) + _ -> messageError $ "unsupported message: " <> tshow event checkSendRcpt :: [AChatMessage] -> CM Bool checkSendRcpt aMsgs = do currentMemCount <- withStore' $ \db -> getGroupCurrentMembersCount db user gInfo @@ -4457,12 +4528,14 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = sendGroupMessage' user gInfo ms msg _ -> pure () RCVD msgMeta msgRcpt -> - withAckMessage' agentConnId msgMeta $ + withAckMessage' "group rcvd" agentConnId msgMeta $ groupMsgReceived gInfo m conn msgMeta msgRcpt SENT msgId proxy -> do + continued <- continueSending connEntity conn sentMsgDeliveryEvent conn msgId checkSndInlineFTComplete conn msgId updateGroupItemStatus gInfo m conn msgId (CISSndSent SSPComplete) (Just $ isJust proxy) + when continued $ sendPendingGroupMessages user m conn SWITCH qd phase cStats -> do toView $ CRGroupMemberSwitch user gInfo m (SwitchProgress qd phase cStats) when (phase `elem` [SPStarted, SPCompleted]) $ case qd of @@ -4498,13 +4571,17 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = OK -> -- [async agent commands] continuation on receiving OK when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () - MWARN msgId err -> + QCONT -> do + continued <- continueSending connEntity conn + when continued $ sendPendingGroupMessages user m conn + MWARN msgId err -> do withStore' $ \db -> updateGroupItemErrorStatus db msgId (groupMemberId' m) (CISSndWarning $ agentSndError err) + processConnMWARN connEntity conn err MERR msgId err -> do withStore' $ \db -> updateGroupItemErrorStatus db msgId (groupMemberId' m) (CISSndError $ agentSndError err) -- group errors are silenced to reduce load on UI event log -- toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) - incAuthErrCounter connEntity conn err + processConnMERR connEntity conn err MERRS msgIds err -> do let newStatus = CISSndError $ agentSndError err -- error cannot be AUTH error here @@ -4581,7 +4658,8 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = lookupChatItemByFileId db vr user fileId toView $ CRSndFileRcvCancelled user ci ft _ -> throwChatError $ CEFileSend fileId err - MSG meta _ _ -> withAckMessage' agentConnId meta $ pure () + MSG meta _ _ -> + withAckMessage' "file msg" agentConnId meta $ pure () OK -> -- [async agent commands] continuation on receiving OK when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () @@ -4633,7 +4711,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () MERR _ err -> do toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) - incAuthErrCounter connEntity conn err + processConnMERR connEntity conn err ERR err -> do toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () @@ -4657,7 +4735,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = RcvChunkOk -> if B.length chunk /= fromInteger chunkSize then badRcvFileChunk ft "incorrect chunk size" - else withAckMessage' agentConnId meta $ appendFileChunk ft chunkNo chunk False + else withAckMessage' "file msg" agentConnId meta $ appendFileChunk ft chunkNo chunk False RcvChunkFinal -> if B.length chunk > fromInteger chunkSize then badRcvFileChunk ft "incorrect chunk size" @@ -4671,7 +4749,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = getChatItemByFileId db vr user fileId toView $ CRRcvFileComplete user ci forM_ conn_ $ \conn -> deleteAgentConnectionAsync user (aConnId conn) - RcvChunkDuplicate -> withAckMessage' agentConnId meta $ pure () + RcvChunkDuplicate -> withAckMessage' "file msg" agentConnId meta $ pure () RcvChunkError -> badRcvFileChunk ft $ "incorrect chunk number " <> show chunkNo processUserContactRequest :: ACommand 'Agent e -> ConnectionEntity -> Connection -> UserContact -> CM () @@ -4685,7 +4763,7 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = _ -> pure () MERR _ err -> do toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) - incAuthErrCounter connEntity conn err + processConnMERR connEntity conn err ERR err -> do toView $ CRChatError (Just user) (ChatErrorAgent err $ Just connEntity) when (corrId /= "") $ withCompletedCommand conn agentMsg $ \_cmdData -> pure () @@ -4725,17 +4803,40 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = | memberRole <= GRObserver = messageError "member is not allowed to send messages" | otherwise = a - incAuthErrCounter :: ConnectionEntity -> Connection -> AgentErrorType -> CM () - incAuthErrCounter connEntity conn err = do + processConnMERR :: ConnectionEntity -> Connection -> AgentErrorType -> CM () + processConnMERR connEntity conn err = do case err of SMP _ SMP.AUTH -> do - authErrCounter' <- withStore' $ \db -> incConnectionAuthErrCounter db user conn + authErrCounter' <- withStore' $ \db -> incAuthErrCounter db user conn when (authErrCounter' >= authErrDisableCount) $ case connEntity of RcvDirectMsgConnection ctConn (Just ct) -> do toView $ CRContactDisabled user ct {activeConn = Just ctConn {authErrCounter = authErrCounter'}} _ -> toView $ CRConnectionDisabled connEntity + SMP _ SMP.QUOTA -> + unless (connInactive conn) $ do + withStore' $ \db -> setQuotaErrCounter db user conn quotaErrSetOnMERR + toView $ CRConnectionInactive connEntity True _ -> pure () + processConnMWARN :: ConnectionEntity -> Connection -> AgentErrorType -> CM () + processConnMWARN connEntity conn err = do + case err of + SMP _ SMP.QUOTA -> + unless (connInactive conn) $ do + quotaErrCounter' <- withStore' $ \db -> incQuotaErrCounter db user conn + when (quotaErrCounter' >= quotaErrInactiveCount) $ + toView $ CRConnectionInactive connEntity True + _ -> pure () + + continueSending :: ConnectionEntity -> Connection -> CM Bool + continueSending connEntity conn = + if connInactive conn + then do + withStore' $ \db -> setQuotaErrCounter db user conn 0 + toView $ CRConnectionInactive connEntity False + pure True + else pure False + -- TODO v5.7 / v6.0 - together with deprecating old group protocol establishing direct connections? -- we could save command records only for agent APIs we process continuations for (INV) withCompletedCommand :: forall e. AEntityI e => Connection -> ACommand 'Agent e -> (CommandData -> CM ()) -> CM () @@ -4755,25 +4856,45 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage = withStore' $ \db -> updateCommandStatus db user cmdId CSError throwChatError . CEAgentCommandError $ msg - withAckMessage' :: ConnId -> MsgMeta -> CM () -> CM () - withAckMessage' cId msgMeta action = do - withAckMessage cId msgMeta False $ action $> False + withAckMessage' :: Text -> ConnId -> MsgMeta -> CM () -> CM () + withAckMessage' label cId msgMeta action = do + withAckMessage label cId msgMeta False Nothing $ \_ -> action $> False - withAckMessage :: ConnId -> MsgMeta -> Bool -> CM Bool -> CM () - withAckMessage cId msgMeta showCritical action = + withAckMessage :: Text -> ConnId -> MsgMeta -> Bool -> Maybe (TVar [Text]) -> (Text -> CM Bool) -> CM () + withAckMessage label cId msgMeta showCritical tags action = do -- [async agent commands] command should be asynchronous -- 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 - Right withRcpt -> ackMsg msgMeta $ if withRcpt then Just "" else Nothing + eInfo <- eventInfo + logInfo $ label <> ": " <> eInfo + tryChatError (action eInfo) >>= \case + Right withRcpt -> + withLog (eInfo <> " ok") $ ackMsg 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 msgMeta Nothing >> throwError e + Left e -> do + withLog (eInfo <> " error: " <> tshow e) $ ackMsg msgMeta Nothing + throwError e where + eventInfo = do + v <- asks eventSeq + eId <- atomically $ stateTVar v $ \i -> (i + 1, i + 1) + pure $ "conn_id=" <> tshow cId <> " event_id=" <> tshow eId + withLog eInfo' ack = do + ts <- showTags + logInfo $ T.unwords [label, "ack:", ts, eInfo'] + ack + logInfo $ T.unwords [label, "ack=success:", ts, eInfo'] + showTags = do + ts <- maybe (pure []) readTVarIO tags + pure $ case ts of + [] -> "no_chat_messages" + [t] -> "chat_message=" <> t + _ -> "chat_message_batch=" <> T.intercalate "," (reverse ts) ackMsg :: MsgMeta -> Maybe MsgReceiptInfo -> CM () ackMsg MsgMeta {recipient = (msgId, _)} rcpt = withAgent $ \a -> ackMessageAsync a "" cId msgId rcpt @@ -6506,16 +6627,20 @@ sendGroupMemberMessages user conn events groupId = do let idsEvts = L.map (GroupId groupId,) events (errs, msgs) <- lift $ partitionEithers . L.toList <$> createSndMessages idsEvts unless (null errs) $ toView $ CRChatErrors (Just user) errs - 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 -> - processSndMessageBatch conn batch `catchChatError` (toView . CRChatError (Just user)) + forM_ (L.nonEmpty msgs) $ \msgs' -> + batchSendGroupMemberMessages user conn msgs' + +batchSendGroupMemberMessages :: User -> Connection -> NonEmpty SndMessage -> CM () +batchSendGroupMemberMessages user conn 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 -> + processSndMessageBatch conn batch `catchChatError` (toView . CRChatError (Just user)) processSndMessageBatch :: Connection -> MsgBatch -> CM () processSndMessageBatch conn@Connection {connId} (MsgBatch batchBody sndMsgs) = do @@ -6561,21 +6686,21 @@ deliverMessage conn cmEventTag msgBody msgId = do deliverMessage' :: Connection -> MsgFlags -> MsgBody -> MessageId -> CM (Int64, PQEncryption) deliverMessage' conn msgFlags msgBody msgId = - lift (deliverMessages ((conn, msgFlags, msgBody, msgId) :| [])) >>= \case + 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 :: NonEmpty MsgReq -> CM' (NonEmpty (Either ChatError (Int64, PQEncryption))) +deliverMessages :: NonEmpty MsgReq -> CM (NonEmpty (Either ChatError (Int64, PQEncryption))) deliverMessages msgs = deliverMessagesB $ L.map Right msgs -deliverMessagesB :: NonEmpty (Either ChatError MsgReq) -> CM' (NonEmpty (Either ChatError (Int64, PQEncryption))) +deliverMessagesB :: NonEmpty (Either ChatError MsgReq) -> CM (NonEmpty (Either ChatError (Int64, PQEncryption))) deliverMessagesB msgReqs = do 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 + sent <- L.zipWith prepareBatch msgReqs' <$> withAgent (`sendMessagesB` L.map toAgent msgReqs') + lift . void $ withStoreBatch' $ \db -> map (updatePQSndEnabled db) (rights . L.toList $ sent) + lift . withStoreBatch $ \db -> L.map (bindRight $ createDelivery db) sent where compressBodies = forME msgReqs $ \mr@(conn@Connection {pqSupport, connChatVersion = v}, msgFlags, msgBody, msgId) -> @@ -6607,6 +6732,8 @@ deliverMessagesB msgReqs = do where updatePQ = updateConnPQSndEnabled db connId pqSndEnabled' +-- TODO combine profile update and message into one batch +-- Take into account that it may not fit, and that we currently don't support sending multiple messages to the same connection in one call. sendGroupMessage :: MsgEncodingI e => User -> GroupInfo -> [GroupMember] -> ChatMsgEvent e -> CM (SndMessage, [GroupMember]) sendGroupMessage user gInfo members chatMsgEvent = do when shouldSendProfileUpdate $ @@ -6634,10 +6761,11 @@ sendGroupMessage' user GroupInfo {groupId} members chatMsgEvent = do msg@SndMessage {msgId, msgBody} <- createSndMessage chatMsgEvent (GroupId groupId) recipientMembers <- liftIO $ shuffleMembers (filter memberCurrent members) let msgFlags = MsgFlags {notification = hasNotification $ toCMEventTag chatMsgEvent} - (toSend, pending) = foldr addMember ([], []) recipientMembers + (toSend, pending, _, dups) = foldr addMember ([], [], S.empty, 0 :: Int) 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 <- maybe (pure []) (fmap L.toList . lift . deliverMessages) $ L.nonEmpty msgReqs + when (dups /= 0) $ logError $ "sendGroupMessage: " <> tshow dups <> " duplicate members" + delivered <- maybe (pure []) (fmap L.toList . deliverMessages) $ L.nonEmpty msgReqs let errors = lefts delivered unless (null errors) $ toView $ CRChatErrors (Just user) errors stored <- lift . withStoreBatch' $ \db -> map (\m -> createPendingGroupMessage db (groupMemberId' m) msgId Nothing) pending @@ -6650,10 +6778,16 @@ sendGroupMessage' user GroupInfo {groupId} members chatMsgEvent = do liftM2 (<>) (shuffle adminMs) (shuffle otherMs) where isAdmin GroupMember {memberRole} = memberRole >= GRAdmin - addMember m (toSend, pending) = case memberSendAction chatMsgEvent members m of - Just (MSASend conn) -> ((m, conn) : toSend, pending) - Just MSAPending -> (toSend, m : pending) - Nothing -> (toSend, pending) + addMember m acc@(toSend, pending, !mIds, !dups) = case memberSendAction chatMsgEvent members m of + Just a + | mId `S.member` mIds -> (toSend, pending, mIds, dups + 1) + | otherwise -> case a of + MSASend conn -> ((m, conn) : toSend, pending, mIds', dups) + MSAPending -> (toSend, m : pending, mIds', dups) + Nothing -> acc + where + mId = groupMemberId' m + mIds' = S.insert mId mIds filterSent :: [Either ChatError a] -> [mem] -> (mem -> GroupMember) -> [GroupMember] filterSent rs ms mem = [mem m | (Right _, m) <- zip rs ms] @@ -6664,6 +6798,7 @@ memberSendAction chatMsgEvent members m@GroupMember {invitedByGroupMemberId} = c Nothing -> pendingOrForwarded Just conn@Connection {connStatus} | connDisabled conn || connStatus == ConnDeleted -> Nothing + | connInactive conn -> Just MSAPending | connStatus == ConnSndReady || connStatus == ConnReady -> Just (MSASend conn) | otherwise -> pendingOrForwarded where @@ -6694,21 +6829,20 @@ sendGroupMemberMessage user m@GroupMember {groupMemberId} chatMsgEvent groupId i MSASend conn -> deliverMessage conn (toCMEventTag chatMsgEvent) msgBody msgId >> postDeliver MSAPending -> withStore' $ \db -> createPendingGroupMessage db groupMemberId msgId introId_ +-- TODO ensure order - pending messages interleave with user input messages sendPendingGroupMessages :: User -> GroupMember -> Connection -> CM () -sendPendingGroupMessages user GroupMember {groupMemberId, localDisplayName} conn = do - pendingMessages <- withStore' $ \db -> getPendingGroupMessages db groupMemberId - -- TODO ensure order - pending messages interleave with user input messages - forM_ pendingMessages $ \pgm -> - processPendingMessage pgm `catchChatError` (toView . CRChatError (Just user)) +sendPendingGroupMessages user GroupMember {groupMemberId} conn = do + pgms <- withStore' $ \db -> getPendingGroupMessages db groupMemberId + forM_ (L.nonEmpty pgms) $ \pgms' -> do + let msgs = L.map (\(sndMsg, _, _) -> sndMsg) pgms' + batchSendGroupMemberMessages user conn msgs + lift . void . withStoreBatch' $ \db -> L.map (\SndMessage {msgId} -> deletePendingGroupMessage db groupMemberId msgId) msgs + lift . void . withStoreBatch' $ \db -> L.map (\(_, tag, introId_) -> updateIntro_ db tag introId_) pgms' where - processPendingMessage PendingGroupMessage {msgId, cmEventTag = ACMEventTag _ tag, msgBody, introId_} = do - void $ deliverMessage conn tag msgBody msgId - withStore' $ \db -> deletePendingGroupMessage db groupMemberId msgId - case tag of - XGrpMemFwd_ -> case introId_ of - Just introId -> withStore' $ \db -> updateIntroStatus db introId GMIntroInvForwarded - _ -> throwChatError $ CEGroupMemberIntroNotFound localDisplayName - _ -> pure () + updateIntro_ :: DB.Connection -> ACMEventTag -> Maybe Int64 -> IO () + updateIntro_ db tag introId_ = case (tag, introId_) of + (ACMEventTag _ XGrpMemFwd_, Just introId) -> updateIntroStatus db introId GMIntroInvForwarded + _ -> pure () -- TODO [batch send] refactor direct message processing same as groups (e.g. checkIntegrity before processing) saveDirectRcvMSG :: Connection -> MsgMeta -> MsgBody -> CM (Connection, RcvMessage) @@ -7245,7 +7379,7 @@ chatCommandP = "/ttl" $> GetChatItemTTL, "/_network info " *> (APISetNetworkInfo <$> jsonP), "/_network " *> (APISetNetworkConfig <$> jsonP), - ("/network " <|> "/net ") *> (APISetNetworkConfig <$> netCfgP), + ("/network " <|> "/net ") *> (SetNetworkConfig <$> netCfgP), ("/network" <|> "/net") $> APIGetNetworkConfig, "/reconnect" $> ReconnectAllServers, "/_settings " *> (APISetChatSettings <$> chatRefP <* A.space <*> jsonP), @@ -7256,6 +7390,10 @@ chatCommandP = ("/info #" <|> "/i #") *> (GroupMemberInfo <$> displayName <* A.space <* char_ '@' <*> displayName), ("/info #" <|> "/i #") *> (ShowGroupInfo <$> displayName), ("/info " <|> "/i ") *> char_ '@' *> (ContactInfo <$> displayName), + "/_queue info #" *> (APIGroupMemberQueueInfo <$> A.decimal <* A.space <*> A.decimal), + "/_queue info @" *> (APIContactQueueInfo <$> A.decimal), + ("/queue info #" <|> "/qi #") *> (GroupMemberQueueInfo <$> displayName <* A.space <* char_ '@' <*> displayName), + ("/queue info " <|> "/qi ") *> char_ '@' *> (ContactQueueInfo <$> displayName), "/_switch #" *> (APISwitchGroupMember <$> A.decimal <* A.space <*> A.decimal), "/_switch @" *> (APISwitchContact <$> A.decimal), "/_abort switch #" *> (APIAbortSwitchGroupMember <$> A.decimal <* A.space <*> A.decimal), @@ -7432,6 +7570,7 @@ chatCommandP = "/get workers" $> GetAgentWorkers, "/get workers details" $> GetAgentWorkersDetails, "/get msgs" $> GetAgentMsgCounts, + "/get queues" $> GetAgentQueuesInfo, "//" *> (CustomChatCommand <$> A.takeByteString) ] where @@ -7553,10 +7692,12 @@ chatCommandP = <|> ("no" $> TMEDisableKeepTTL) netCfgP = do socksProxy <- "socks=" *> ("off" $> Nothing <|> "on" $> Just defaultSocksProxy <|> Just <$> strP) + smpProxyMode_ <- optional $ " smp-proxy=" *> strP + smpProxyFallback_ <- optional $ " smp-proxy-fallback=" *> strP t_ <- optional $ " timeout=" *> A.decimal - logErrors <- " log=" *> onOffP <|> pure False - let tcpTimeout = 1000000 * fromMaybe (maybe 5 (const 10) socksProxy) t_ - pure $ fullNetworkConfig socksProxy tcpTimeout logErrors + logTLSErrors <- " log=" *> onOffP <|> pure False + let tcpTimeout_ = (1000000 *) <$> t_ + pure $ SimpleNetCfg {socksProxy, smpProxyMode_, smpProxyFallback_, tcpTimeout_, logTLSErrors} dbKeyP = nonEmptyKey <$?> strP nonEmptyKey k@(DBEncryptionKey s) = if BA.null s then Left "empty key" else Right k dbEncryptionConfig currentKey newKey = DBEncryptionConfig {currentKey, newKey, keepKey = Just False} diff --git a/src/Simplex/Chat/Archive.hs b/src/Simplex/Chat/Archive.hs index 8550c03438..01897de791 100644 --- a/src/Simplex/Chat/Archive.hs +++ b/src/Simplex/Chat/Archive.hs @@ -26,6 +26,7 @@ import Data.Text (Text) import qualified Data.Text as T import qualified Database.SQLite3 as SQL import Simplex.Chat.Controller +import Simplex.Chat.Util () import Simplex.Messaging.Agent.Client (agentClientStore) import Simplex.Messaging.Agent.Store.SQLite (SQLiteStore (..), closeSQLiteStore, keyString, sqlString, storeKey) import Simplex.Messaging.Util diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs index 9ff903514f..77b14b5eac 100644 --- a/src/Simplex/Chat/Controller.hs +++ b/src/Simplex/Chat/Controller.hs @@ -13,6 +13,7 @@ {-# LANGUAGE StrictData #-} {-# LANGUAGE TemplateHaskell #-} {-# LANGUAGE TypeApplications #-} +{-# OPTIONS_GHC -fno-warn-implicit-lift #-} module Simplex.Chat.Controller where @@ -67,13 +68,14 @@ import Simplex.Chat.Types.UITheme import Simplex.Chat.Util (liftIOEither) import Simplex.FileTransfer.Description (FileDescriptionURI) import Simplex.Messaging.Agent (AgentClient, SubscriptionsInfo) -import Simplex.Messaging.Agent.Client (AgentLocks, AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure, UserNetworkInfo) +import Simplex.Messaging.Agent.Client (AgentLocks, AgentQueuesInfo (..), AgentWorkersDetails (..), AgentWorkersSummary (..), ProtocolTestFailure, UserNetworkInfo) import Simplex.Messaging.Agent.Env.SQLite (AgentConfig, NetworkConfig) import Simplex.Messaging.Agent.Lock import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation, SQLiteStore, UpMigration, withTransaction) import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB +import Simplex.Messaging.Client (SMPProxyMode (..), SMPProxyFallback (..)) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..)) import qualified Simplex.Messaging.Crypto.File as CF @@ -82,9 +84,10 @@ import Simplex.Messaging.Encoding.String import Simplex.Messaging.Notifications.Protocol (DeviceToken (..), NtfTknStatus) import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, enumJSON, parseAll, parseString, sumTypeJSON) import Simplex.Messaging.Protocol (AProtoServerWithAuth, AProtocolType (..), CorrId, NtfServer, ProtoServerWithAuth, ProtocolTypeI, QueueId, SMPMsgMeta (..), SProtocolType, SubscriptionMode (..), UserProtocol, XFTPServer, XFTPServerWithAuth, userProtocol) +import Simplex.Messaging.Server.QueueStore.QueueInfo import Simplex.Messaging.TMap (TMap) import Simplex.Messaging.Transport (TLS, simplexMQVersion) -import Simplex.Messaging.Transport.Client (TransportHost) +import Simplex.Messaging.Transport.Client (SocksProxy, TransportHost) import Simplex.Messaging.Util (allFinally, catchAllErrors, catchAllErrors', tryAllErrors, tryAllErrors', (<$$>)) import Simplex.RemoteControl.Client import Simplex.RemoteControl.Invitation (RCSignedInvitation, RCVerifiedInvitation) @@ -205,6 +208,7 @@ data ChatController = ChatController chatStore :: SQLiteStore, chatStoreChanged :: TVar Bool, -- if True, chat should be fully restarted random :: TVar ChaChaDRG, + eventSeq :: TVar Int, inputQ :: TBQueue String, outputQ :: TBQueue (Maybe CorrId, Maybe RemoteHostId, ChatResponse), connNetworkStatuses :: TMap AgentConnId NetworkStatus, @@ -348,6 +352,7 @@ data ChatCommand | GetChatItemTTL | APISetNetworkConfig NetworkConfig | APIGetNetworkConfig + | SetNetworkConfig SimpleNetCfg | APISetNetworkInfo UserNetworkInfo | ReconnectAllServers | APISetChatSettings ChatRef ChatSettings @@ -355,6 +360,8 @@ data ChatCommand | APIContactInfo ContactId | APIGroupInfo GroupId | APIGroupMemberInfo GroupId GroupMemberId + | APIContactQueueInfo ContactId + | APIGroupMemberQueueInfo GroupId GroupMemberId | APISwitchContact ContactId | APISwitchGroupMember GroupId GroupMemberId | APIAbortSwitchContact ContactId @@ -373,6 +380,8 @@ data ChatCommand | ContactInfo ContactName | ShowGroupInfo GroupName | GroupMemberInfo GroupName ContactName + | ContactQueueInfo ContactName + | GroupMemberQueueInfo GroupName ContactName | SwitchContact ContactName | SwitchGroupMember GroupName ContactName | AbortSwitchContact ContactName @@ -502,6 +511,7 @@ data ChatCommand | GetAgentWorkers | GetAgentWorkersDetails | GetAgentMsgCounts + | GetAgentQueuesInfo | -- The parser will return this command for strings that start from "//". -- This command should be processed in preCmdHook CustomChatCommand ByteString @@ -566,6 +576,7 @@ data ChatResponse | CRContactInfo {user :: User, contact :: Contact, connectionStats_ :: Maybe ConnectionStats, customUserProfile :: Maybe Profile} | CRGroupInfo {user :: User, groupInfo :: GroupInfo, groupSummary :: GroupSummary} | CRGroupMemberInfo {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats_ :: Maybe ConnectionStats} + | CRQueueInfo {user :: User, rcvMsgInfo :: Maybe RcvMsgInfo, queueInfo :: QueueInfo} | CRContactSwitchStarted {user :: User, contact :: Contact, connectionStats :: ConnectionStats} | CRGroupMemberSwitchStarted {user :: User, groupInfo :: GroupInfo, member :: GroupMember, connectionStats :: ConnectionStats} | CRContactSwitchAborted {user :: User, contact :: Contact, connectionStats :: ConnectionStats} @@ -748,8 +759,10 @@ data ChatResponse | CRAgentSubs {activeSubs :: Map Text Int, pendingSubs :: Map Text Int, removedSubs :: Map Text [String]} | CRAgentSubsDetails {agentSubs :: SubscriptionsInfo} | CRAgentMsgCounts {msgCounts :: [(Text, (Int, Int))]} + | CRAgentQueuesInfo {agentQueuesInfo :: AgentQueuesInfo} | CRContactDisabled {user :: User, contact :: Contact} | CRConnectionDisabled {connectionEntity :: ConnectionEntity} + | CRConnectionInactive {connectionEntity :: ConnectionEntity, inactive :: Bool} | CRAgentRcvQueueDeleted {agentConnId :: AgentConnId, server :: SMPServer, agentQueueId :: AgentQueueId, agentError_ :: Maybe AgentErrorType} | CRAgentConnDeleted {agentConnId :: AgentConnId} | CRAgentUserDeleted {agentUserId :: Int64} @@ -950,6 +963,18 @@ data AppFilePathsConfig = AppFilePathsConfig } deriving (Show) +data SimpleNetCfg = SimpleNetCfg + { socksProxy :: Maybe SocksProxy, + smpProxyMode_ :: Maybe SMPProxyMode, + smpProxyFallback_ :: Maybe SMPProxyFallback, + tcpTimeout_ :: Maybe Int, + logTLSErrors :: Bool + } + deriving (Show) + +defaultSimpleNetCfg :: SimpleNetCfg +defaultSimpleNetCfg = SimpleNetCfg Nothing Nothing Nothing Nothing False + data ContactSubStatus = ContactSubStatus { contact :: Contact, contactError :: Maybe ChatError @@ -1115,7 +1140,6 @@ data ChatErrorType | CECantBlockMemberForSelf {groupInfo :: GroupInfo, member :: GroupMember, setShowMessages :: Bool} | CEGroupMemberUserRemoved | CEGroupMemberNotFound - | CEGroupMemberIntroNotFound {contactName :: ContactName} | CEGroupCantResendInvitation {groupInfo :: GroupInfo, contactName :: ContactName} | CEGroupInternal {message :: String} | CEFileNotFound {message :: String} diff --git a/src/Simplex/Chat/Messages.hs b/src/Simplex/Chat/Messages.hs index 449731b91c..ea79161e06 100644 --- a/src/Simplex/Chat/Messages.hs +++ b/src/Simplex/Chat/Messages.hs @@ -14,6 +14,7 @@ {-# LANGUAGE TypeOperators #-} {-# LANGUAGE UndecidableInstances #-} {-# OPTIONS_GHC -fno-warn-ambiguous-fields #-} +{-# OPTIONS_GHC -fno-warn-operator-whitespace #-} module Simplex.Chat.Messages where @@ -455,10 +456,10 @@ deriving instance Show ACIReaction data JSONCIReaction c d = JSONCIReaction {chatInfo :: ChatInfo c, chatReaction :: CIReaction c d} type family ChatTypeQuotable (a :: ChatType) :: Constraint where - ChatTypeQuotable CTDirect = () - ChatTypeQuotable CTGroup = () + ChatTypeQuotable 'CTDirect = () + ChatTypeQuotable 'CTGroup = () ChatTypeQuotable a = - (Int ~ Bool, TypeError (Type.Text "ChatType " :<>: ShowType a :<>: Type.Text " cannot be quoted")) + (Int ~ Bool, TypeError ('Type.Text "ChatType " ':<>: 'ShowType a ':<>: 'Type.Text " cannot be quoted")) data CIQDirection (c :: ChatType) where CIQDirectSnd :: CIQDirection 'CTDirect @@ -944,13 +945,6 @@ data RcvMessage = RcvMessage forwardedByMember :: Maybe GroupMemberId } -data PendingGroupMessage = PendingGroupMessage - { msgId :: MessageId, - cmEventTag :: ACMEventTag, - msgBody :: MsgBody, - introId_ :: Maybe Int64 - } - type MessageId = Int64 data ConnOrGroupId = ConnectionId Int64 | GroupId Int64 @@ -968,6 +962,15 @@ data RcvMsgDelivery = RcvMsgDelivery } deriving (Show) +data RcvMsgInfo = RcvMsgInfo + { msgId :: Int64, + msgDeliveryId :: Int64, + msgDeliveryStatus :: Text, + agentMsgId :: AgentMsgId, + agentMsgMeta :: Text + } + deriving (Show) + data MsgMetaJSON = MsgMetaJSON { integrity :: Text, rcvId :: Int64, @@ -1338,3 +1341,5 @@ $(JQ.deriveJSON defaultJSON ''MsgMetaJSON) msgMetaJson :: MsgMeta -> Text msgMetaJson = decodeLatin1 . LB.toStrict . J.encode . msgMetaToJson + +$(JQ.deriveJSON defaultJSON ''RcvMsgInfo) diff --git a/src/Simplex/Chat/Migrations/M20240528_quota_err_counter.hs b/src/Simplex/Chat/Migrations/M20240528_quota_err_counter.hs new file mode 100644 index 0000000000..ea1f3a78e7 --- /dev/null +++ b/src/Simplex/Chat/Migrations/M20240528_quota_err_counter.hs @@ -0,0 +1,18 @@ +{-# LANGUAGE QuasiQuotes #-} + +module Simplex.Chat.Migrations.M20240528_quota_err_counter where + +import Database.SQLite.Simple (Query) +import Database.SQLite.Simple.QQ (sql) + +m20240528_quota_err_counter :: Query +m20240528_quota_err_counter = + [sql| +ALTER TABLE connections ADD COLUMN quota_err_counter INTEGER NOT NULL DEFAULT 0; +|] + +down_m20240528_quota_err_counter :: Query +down_m20240528_quota_err_counter = + [sql| +ALTER TABLE connections DROP COLUMN quota_err_counter; +|] diff --git a/src/Simplex/Chat/Migrations/chat_schema.sql b/src/Simplex/Chat/Migrations/chat_schema.sql index 96d55badf9..fdbc44a9c3 100644 --- a/src/Simplex/Chat/Migrations/chat_schema.sql +++ b/src/Simplex/Chat/Migrations/chat_schema.sql @@ -289,6 +289,7 @@ CREATE TABLE connections( pq_encryption INTEGER NOT NULL DEFAULT 0, pq_snd_enabled INTEGER, pq_rcv_enabled INTEGER, + quota_err_counter INTEGER NOT NULL DEFAULT 0, FOREIGN KEY(snd_file_id, connection_id) REFERENCES snd_files(file_id, connection_id) ON DELETE CASCADE diff --git a/src/Simplex/Chat/Mobile.hs b/src/Simplex/Chat/Mobile.hs index 5883c6042c..486e0d62f3 100644 --- a/src/Simplex/Chat/Mobile.hs +++ b/src/Simplex/Chat/Mobile.hs @@ -48,7 +48,6 @@ import Simplex.Chat.Types import Simplex.Messaging.Agent.Client (agentClientStore) import Simplex.Messaging.Agent.Env.SQLite (createAgentStore) import Simplex.Messaging.Agent.Store.SQLite (MigrationConfirmation (..), MigrationError, closeSQLiteStore, reopenSQLiteStore) -import Simplex.Messaging.Client (defaultNetworkConfig) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (defaultJSON, dropPrefix, sumTypeJSON) @@ -192,7 +191,7 @@ mobileChatOpts dbFilePrefix = dbKey = "", -- for API database is already opened, and the key in options is not used smpServers = [], xftpServers = [], - networkConfig = defaultNetworkConfig, + simpleNetCfg = defaultSimpleNetCfg, logLevel = CLLImportant, logConnections = False, logServerHosts = True, diff --git a/src/Simplex/Chat/Options.hs b/src/Simplex/Chat/Options.hs index 871e3358ec..747414af37 100644 --- a/src/Simplex/Chat/Options.hs +++ b/src/Simplex/Chat/Options.hs @@ -13,7 +13,6 @@ module Simplex.Chat.Options coreChatOptsP, getChatOpts, protocolServersP, - fullNetworkConfig, ) where @@ -22,15 +21,17 @@ import qualified Data.Attoparsec.ByteString.Char8 as A import Data.ByteArray (ScrubbedBytes) import qualified Data.ByteString.Char8 as B import Data.Text (Text) +import qualified Data.Text as T +import Data.Text.Encoding (encodeUtf8) import Numeric.Natural (Natural) import Options.Applicative -import Simplex.Chat.Controller (ChatLogLevel (..), updateStr, versionNumber, versionString) +import Simplex.Chat.Controller (ChatLogLevel (..), SimpleNetCfg (..), updateStr, versionNumber, versionString) import Simplex.FileTransfer.Description (mb) -import Simplex.Messaging.Client (NetworkConfig (..), defaultNetworkConfig) +import Simplex.Messaging.Client (SMPProxyMode (..), SMPProxyFallback (..)) import Simplex.Messaging.Encoding.String import Simplex.Messaging.Parsers (parseAll) import Simplex.Messaging.Protocol (ProtoServerWithAuth, ProtocolTypeI, SMPServerWithAuth, XFTPServerWithAuth) -import Simplex.Messaging.Transport.Client (SocksProxy, defaultSocksProxy) +import Simplex.Messaging.Transport.Client (defaultSocksProxy) import System.FilePath (combine) data ChatOpts = ChatOpts @@ -55,7 +56,7 @@ data CoreChatOpts = CoreChatOpts dbKey :: ScrubbedBytes, smpServers :: [SMPServerWithAuth], xftpServers :: [XFTPServerWithAuth], - networkConfig :: NetworkConfig, + simpleNetCfg :: SimpleNetCfg, logLevel :: ChatLogLevel, logConnections :: Bool, logServerHosts :: Bool, @@ -123,18 +124,34 @@ coreChatOptsP appDir defaultDbFileName = do socksProxy <- flag' (Just defaultSocksProxy) (short 'x' <> help "Use local SOCKS5 proxy at :9050") <|> option - parseSocksProxy + strParse ( long "socks-proxy" <> metavar "SOCKS5" <> help "Use SOCKS5 proxy at `ipv4:port` or `:port`" <> value Nothing ) + smpProxyMode_ <- + optional $ + option + strParse + ( long "smp-proxy" + <> metavar "SMP_PROXY_MODE" + <> help "Use private message routing: always, unknown, unprotected, never (default)" + ) + smpProxyFallback_ <- + optional $ + option + strParse + ( long "smp-proxy-fallback" + <> metavar "SMP_PROXY_FALLBACK_MODE" + <> help "Allow downgrade and connect directly: no, [when IP address is] protected, yes (default)" + ) t <- option auto ( long "tcp-timeout" <> metavar "TIMEOUT" - <> help "TCP timeout, seconds (default: 5/10 without/with SOCKS5 proxy)" + <> help "TCP timeout, seconds (default: 7/15 without/with SOCKS5 proxy)" <> value 0 ) logLevel <- @@ -149,7 +166,7 @@ coreChatOptsP appDir defaultDbFileName = do logTLSErrors <- switch ( long "log-tls-errors" - <> help "Log TLS errors (also enabled with `-l debug`)" + <> help "Log TLS errors" ) logConnections <- switch @@ -194,7 +211,7 @@ coreChatOptsP appDir defaultDbFileName = do dbKey, smpServers, xftpServers, - networkConfig = fullNetworkConfig socksProxy (useTcpTimeout socksProxy t) (logTLSErrors || logLevel == CLLDebug), + simpleNetCfg = SimpleNetCfg {socksProxy, smpProxyMode_, smpProxyFallback_, tcpTimeout_ = Just $ useTcpTimeout socksProxy t, logTLSErrors}, logLevel, logConnections = logConnections || logLevel <= CLLInfo, logServerHosts = logServerHosts || logLevel <= CLLInfo, @@ -204,7 +221,7 @@ coreChatOptsP appDir defaultDbFileName = do highlyAvailable } where - useTcpTimeout p t = 1000000 * if t > 0 then t else maybe 5 (const 10) p + useTcpTimeout p t = 1000000 * if t > 0 then t else maybe 7 (const 15) p defaultDbFilePath = combine appDir defaultDbFileName chatOptsP :: FilePath -> FilePath -> Parser ChatOpts @@ -321,16 +338,11 @@ chatOptsP appDir defaultDbFileName = do maintenance } -fullNetworkConfig :: Maybe SocksProxy -> Int -> Bool -> NetworkConfig -fullNetworkConfig socksProxy tcpTimeout logTLSErrors = - let tcpConnectTimeout = (tcpTimeout * 3) `div` 2 - in defaultNetworkConfig {socksProxy, tcpTimeout, tcpConnectTimeout, logTLSErrors} - parseProtocolServers :: ProtocolTypeI p => ReadM [ProtoServerWithAuth p] parseProtocolServers = eitherReader $ parseAll protocolServersP . B.pack -parseSocksProxy :: ReadM (Maybe SocksProxy) -parseSocksProxy = eitherReader $ parseAll strP . B.pack +strParse :: StrEncoding a => ReadM a +strParse = eitherReader $ parseAll strP . encodeUtf8 . T.pack parseServerPort :: ReadM (Maybe String) parseServerPort = eitherReader $ parseAll serverPortP . B.pack diff --git a/src/Simplex/Chat/Remote.hs b/src/Simplex/Chat/Remote.hs index e8d13402ef..064fcc561e 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, 7, 0, 3] +minRemoteCtrlVersion = AppVersion [5, 8, 0, 4] -- when acting as controller minRemoteHostVersion :: AppVersion -minRemoteHostVersion = AppVersion [5, 7, 0, 3] +minRemoteHostVersion = AppVersion [5, 8, 0, 4] currentAppVersion :: AppVersion currentAppVersion = AppVersion SC.version diff --git a/src/Simplex/Chat/Store/Connections.hs b/src/Simplex/Chat/Store/Connections.hs index 1c3e949562..2c7543f08a 100644 --- a/src/Simplex/Chat/Store/Connections.hs +++ b/src/Simplex/Chat/Store/Connections.hs @@ -14,6 +14,7 @@ module Simplex.Chat.Store.Connections getContactConnEntityByConnReqHash, getConnectionsToSubscribe, unsetConnectionToSubscribe, + deleteConnectionRecord, ) where @@ -84,7 +85,7 @@ getConnectionEntity db vr user@User {userId, userContactId} agentConnId = do [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, pq_support, pq_encryption, pq_snd_enabled, pq_rcv_enabled, auth_err_counter, + created_at, security_code, security_code_verified_at, pq_support, pq_encryption, pq_snd_enabled, pq_rcv_enabled, auth_err_counter, quota_err_counter, conn_chat_version, peer_chat_min_version, peer_chat_max_version FROM connections WHERE user_id = ? AND agent_conn_id = ? @@ -225,3 +226,7 @@ getConnectionsToSubscribe db vr = do unsetConnectionToSubscribe :: DB.Connection -> IO () unsetConnectionToSubscribe db = DB.execute_ db "UPDATE connections SET to_subscribe = 0 WHERE to_subscribe = 1" + +deleteConnectionRecord :: DB.Connection -> User -> Int64 -> IO () +deleteConnectionRecord db User {userId} cId = do + DB.execute db "DELETE FROM connections WHERE user_id = ? AND connection_id = ?" (userId, cId) diff --git a/src/Simplex/Chat/Store/Direct.hs b/src/Simplex/Chat/Store/Direct.hs index ecba9be7fa..1145c2494b 100644 --- a/src/Simplex/Chat/Store/Direct.hs +++ b/src/Simplex/Chat/Store/Direct.hs @@ -51,8 +51,10 @@ module Simplex.Chat.Store.Direct updateContactStatus, updateGroupUnreadChat, setConnectionVerified, - incConnectionAuthErrCounter, - setConnectionAuthErrCounter, + incAuthErrCounter, + setAuthErrCounter, + incQuotaErrCounter, + setQuotaErrCounter, getUserContacts, createOrUpdateContactRequest, getContactRequest', @@ -130,11 +132,11 @@ deletePendingContactConnection db userId connId = |] (userId, connId, ConnContact) -createAddressContactConnection :: DB.Connection -> VersionRangeChat -> User -> Contact -> ConnId -> ConnReqUriHash -> XContactId -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> ExceptT StoreError IO Contact +createAddressContactConnection :: DB.Connection -> VersionRangeChat -> User -> Contact -> ConnId -> ConnReqUriHash -> XContactId -> Maybe Profile -> SubscriptionMode -> VersionChat -> PQSupport -> ExceptT StoreError IO (Int64, Contact) createAddressContactConnection db vr user@User {userId} Contact {contactId} acId cReqHash xContactId incognitoProfile subMode chatV pqSup = do PendingContactConnection {pccConnId} <- liftIO $ createConnReqConnection db userId acId cReqHash xContactId incognitoProfile Nothing subMode chatV pqSup liftIO $ DB.execute db "UPDATE connections SET contact_id = ? WHERE connection_id = ?" (contactId, pccConnId) - getContact db vr user contactId + (pccConnId,) <$> getContact db vr user contactId createConnReqConnection :: DB.Connection -> UserId -> ConnId -> ConnReqUriHash -> XContactId -> Maybe Profile -> Maybe GroupLinkId -> SubscriptionMode -> VersionChat -> PQSupport -> IO PendingContactConnection createConnReqConnection db userId acId cReqHash xContactId incognitoProfile groupLinkId subMode chatV pqSup = do @@ -183,7 +185,7 @@ getContactByConnReqHash db vr 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, ct.ui_themes, ct.chat_deleted, ct.custom_data, -- 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.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + 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.quota_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 @@ -473,19 +475,32 @@ setConnectionVerified db User {userId} connId code = do updatedAt <- getCurrentTime DB.execute db "UPDATE connections SET security_code = ?, security_code_verified_at = ?, updated_at = ? WHERE user_id = ? AND connection_id = ?" (code, code $> updatedAt, updatedAt, userId, connId) -incConnectionAuthErrCounter :: DB.Connection -> User -> Connection -> IO Int -incConnectionAuthErrCounter db User {userId} Connection {connId, authErrCounter} = do +incAuthErrCounter :: DB.Connection -> User -> Connection -> IO Int +incAuthErrCounter db User {userId} Connection {connId, authErrCounter} = do updatedAt <- getCurrentTime (counter_ :: Maybe Int) <- maybeFirstRow fromOnly $ DB.query db "SELECT auth_err_counter FROM connections WHERE user_id = ? AND connection_id = ?" (userId, connId) let counter' = fromMaybe authErrCounter counter_ + 1 DB.execute db "UPDATE connections SET auth_err_counter = ?, updated_at = ? WHERE user_id = ? AND connection_id = ?" (counter', updatedAt, userId, connId) pure counter' -setConnectionAuthErrCounter :: DB.Connection -> User -> Connection -> Int -> IO () -setConnectionAuthErrCounter db User {userId} Connection {connId} counter = do +setAuthErrCounter :: DB.Connection -> User -> Connection -> Int -> IO () +setAuthErrCounter db User {userId} Connection {connId} counter = do updatedAt <- getCurrentTime DB.execute db "UPDATE connections SET auth_err_counter = ?, updated_at = ? WHERE user_id = ? AND connection_id = ?" (counter, updatedAt, userId, connId) +incQuotaErrCounter :: DB.Connection -> User -> Connection -> IO Int +incQuotaErrCounter db User {userId} Connection {connId, quotaErrCounter} = do + updatedAt <- getCurrentTime + (counter_ :: Maybe Int) <- maybeFirstRow fromOnly $ DB.query db "SELECT quota_err_counter FROM connections WHERE user_id = ? AND connection_id = ?" (userId, connId) + let counter' = fromMaybe quotaErrCounter counter_ + 1 + DB.execute db "UPDATE connections SET quota_err_counter = ?, updated_at = ? WHERE user_id = ? AND connection_id = ?" (counter', updatedAt, userId, connId) + pure counter' + +setQuotaErrCounter :: DB.Connection -> User -> Connection -> Int -> IO () +setQuotaErrCounter db User {userId} Connection {connId} counter = do + updatedAt <- getCurrentTime + DB.execute db "UPDATE connections SET quota_err_counter = ?, updated_at = ? WHERE user_id = ? AND connection_id = ?" (counter, updatedAt, userId, connId) + updateContactProfile_ :: DB.Connection -> UserId -> ProfileId -> Profile -> IO () updateContactProfile_ db userId profileId profile = do currentTs <- getCurrentTime @@ -609,7 +624,7 @@ createOrUpdateContactRequest db vr user@User {userId} userContactLinkId invId (V cp.preferences, ct.user_preferences, ct.created_at, ct.updated_at, ct.chat_ts, ct.contact_group_member_id, ct.contact_grp_inv_sent, ct.ui_themes, ct.chat_deleted, ct.custom_data, -- 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.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + 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.quota_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 @@ -794,7 +809,7 @@ getContact_ db vr 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, ct.ui_themes, ct.chat_deleted, ct.custom_data, -- 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.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + 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.quota_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 @@ -848,7 +863,7 @@ getContactConnections db vr userId Contact {contactId} = [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.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + 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.quota_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 @@ -866,7 +881,7 @@ getConnectionById db vr User {userId} connId = ExceptT $ do [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, pq_support, pq_encryption, pq_snd_enabled, pq_rcv_enabled, auth_err_counter, + created_at, security_code, security_code_verified_at, pq_support, pq_encryption, pq_snd_enabled, pq_rcv_enabled, auth_err_counter, quota_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/Groups.hs b/src/Simplex/Chat/Store/Groups.hs index 5e603a40c9..42637d4169 100644 --- a/src/Simplex/Chat/Store/Groups.hs +++ b/src/Simplex/Chat/Store/Groups.hs @@ -201,7 +201,7 @@ getGroupLinkConnection db vr User {userId} groupInfo@GroupInfo {groupId} = [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.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + 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.quota_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 @@ -287,7 +287,7 @@ getGroupAndMember db User {userId, userContactId} groupMemberId vr = 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.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + 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.quota_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) @@ -705,7 +705,7 @@ groupMemberQuery = 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.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + 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.quota_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) @@ -944,10 +944,10 @@ 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 -> VersionChat -> VersionRangeChat -> SubscriptionMode -> IO () +createMemberConnection :: DB.Connection -> UserId -> GroupMember -> ConnId -> VersionChat -> VersionRangeChat -> SubscriptionMode -> IO Connection createMemberConnection db userId GroupMember {groupMemberId} agentConnId chatV peerChatVRange subMode = do currentTs <- getCurrentTime - void $ createMemberConnection_ db userId groupMemberId agentConnId chatV peerChatVRange Nothing 0 currentTs subMode + createMemberConnection_ db userId groupMemberId agentConnId chatV peerChatVRange Nothing 0 currentTs subMode createMemberConnectionAsync :: DB.Connection -> User -> GroupMemberId -> (CommandId, ConnId) -> VersionChat -> VersionRangeChat -> SubscriptionMode -> IO () createMemberConnectionAsync db user@User {userId} groupMemberId (cmdId, agentConnId) chatV peerChatVRange subMode = do @@ -1090,20 +1090,33 @@ createIntroductions db chatV members toMember = do then pure [] else do currentTs <- getCurrentTime - mapM (insertIntro_ currentTs) reMembers + catMaybes <$> mapM (createIntro_ currentTs) reMembers where - insertIntro_ :: UTCTime -> GroupMember -> IO GroupMemberIntro - insertIntro_ ts reMember = do - DB.execute - db - [sql| - INSERT INTO group_member_intros - (re_group_member_id, to_group_member_id, intro_status, intro_chat_protocol_version, created_at, updated_at) - VALUES (?,?,?,?,?,?) - |] - (groupMemberId' reMember, groupMemberId' toMember, GMIntroPending, chatV, ts, ts) - introId <- insertedRowId db - pure GroupMemberIntro {introId, reMember, toMember, introStatus = GMIntroPending, introInvitation = Nothing} + createIntro_ :: UTCTime -> GroupMember -> IO (Maybe GroupMemberIntro) + createIntro_ ts reMember = + -- when members connect concurrently, host would try to create introductions between them in both directions; + -- this check avoids creating second (redundant) introduction + checkInverseIntro >>= \case + Just _ -> pure Nothing + Nothing -> do + DB.execute + db + [sql| + INSERT INTO group_member_intros + (re_group_member_id, to_group_member_id, intro_status, intro_chat_protocol_version, created_at, updated_at) + VALUES (?,?,?,?,?,?) + |] + (groupMemberId' reMember, groupMemberId' toMember, GMIntroPending, chatV, ts, ts) + introId <- insertedRowId db + pure $ Just GroupMemberIntro {introId, reMember, toMember, introStatus = GMIntroPending, introInvitation = Nothing} + where + checkInverseIntro :: IO (Maybe Int64) + checkInverseIntro = + maybeFirstRow fromOnly $ + DB.query + db + "SELECT 1 FROM group_member_intros WHERE re_group_member_id = ? AND to_group_member_id = ? LIMIT 1" + (groupMemberId' toMember, groupMemberId' reMember) updateIntroStatus :: DB.Connection -> Int64 -> GroupMemberIntroStatus -> IO () updateIntroStatus db introId introStatus = do @@ -1313,7 +1326,7 @@ getViaGroupMember db vr User {userId, userContactId} Contact {contactId} = 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.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + 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.quota_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 @@ -1957,7 +1970,8 @@ createMemberContact pqEncryption = PQEncOff, pqSndEnabled = Nothing, pqRcvEnabled = Nothing, - authErrCounter = 0 + authErrCounter = 0, + quotaErrCounter = 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, uiThemes = Nothing, chatDeleted = False, customData = Nothing} @@ -2090,7 +2104,8 @@ createMemberContactConn_ pqEncryption = PQEncOff, pqSndEnabled = Nothing, pqRcvEnabled = Nothing, - authErrCounter = 0 + authErrCounter = 0, + quotaErrCounter = 0 } updateMemberProfile :: DB.Connection -> User -> GroupMember -> Profile -> ExceptT StoreError IO GroupMember diff --git a/src/Simplex/Chat/Store/Messages.hs b/src/Simplex/Chat/Store/Messages.hs index b0a0495c16..853d34995a 100644 --- a/src/Simplex/Chat/Store/Messages.hs +++ b/src/Simplex/Chat/Store/Messages.hs @@ -23,6 +23,7 @@ module Simplex.Chat.Store.Messages createNewSndMessage, createSndMsgDelivery, createNewMessageAndRcvMsgDelivery, + getLastRcvMsgInfo, createNewRcvMessage, updateSndMsgDeliveryStatus, createPendingGroupMessage, @@ -226,6 +227,23 @@ createNewMessageAndRcvMsgDelivery db connOrGroupId newMessage sharedMsgId_ RcvMs (msgId, connId, agentMsgId, msgMetaJson agentMsgMeta, snd $ broker agentMsgMeta, currentTs, currentTs, MDSRcvAgent) pure msg +getLastRcvMsgInfo :: DB.Connection -> Int64 -> IO (Maybe RcvMsgInfo) +getLastRcvMsgInfo db connId = + maybeFirstRow rcvMsgInfo $ + DB.query + db + [sql| + SELECT message_id, msg_delivery_id, delivery_status, agent_msg_id, agent_msg_meta + FROM msg_deliveries + WHERE connection_id = ? AND delivery_status IN (?, ?) + ORDER BY created_at DESC, msg_delivery_id DESC + LIMIT 1 + |] + (connId, MDSRcvAgent, MDSRcvAcknowledged) + where + rcvMsgInfo (msgId, msgDeliveryId, msgDeliveryStatus, agentMsgId, agentMsgMeta) = + RcvMsgInfo {msgId, msgDeliveryId, msgDeliveryStatus, agentMsgId, agentMsgMeta} + createNewRcvMessage :: forall e. MsgEncodingI e => DB.Connection -> ConnOrGroupId -> NewRcvMessage e -> Maybe SharedMsgId -> Maybe GroupMemberId -> Maybe GroupMemberId -> ExceptT StoreError IO RcvMessage createNewRcvMessage db connOrGroupId NewRcvMessage {chatMsgEvent, msgBody} sharedMsgId_ authorMember forwardedByMember = case connOrGroupId of @@ -285,22 +303,22 @@ createPendingGroupMessage db groupMemberId messageId introId_ = do |] (groupMemberId, messageId, introId_, currentTs, currentTs) -getPendingGroupMessages :: DB.Connection -> Int64 -> IO [PendingGroupMessage] +getPendingGroupMessages :: DB.Connection -> Int64 -> IO [(SndMessage, ACMEventTag, Maybe Int64)] getPendingGroupMessages db groupMemberId = map pendingGroupMessage <$> DB.query db [sql| - SELECT pgm.message_id, m.chat_msg_event, m.msg_body, pgm.group_member_intro_id + SELECT pgm.message_id, m.shared_msg_id, m.msg_body, m.chat_msg_event, pgm.group_member_intro_id FROM pending_group_messages pgm JOIN messages m USING (message_id) WHERE pgm.group_member_id = ? - ORDER BY pgm.message_id ASC + ORDER BY pgm.created_at ASC, pgm.message_id ASC |] (Only groupMemberId) where - pendingGroupMessage (msgId, cmEventTag, msgBody, introId_) = - PendingGroupMessage {msgId, cmEventTag, msgBody, introId_} + pendingGroupMessage (msgId, sharedMsgId, msgBody, cmEventTag, introId_) = + (SndMessage {msgId, sharedMsgId, msgBody}, cmEventTag, introId_) deletePendingGroupMessage :: DB.Connection -> Int64 -> MessageId -> IO () deletePendingGroupMessage db groupMemberId messageId = @@ -838,7 +856,7 @@ toLocalChatItem currentTs ((itemId, itemTs, AMsgDirection msgDir, itemContentTex ciMeta content status = let itemDeleted' = case itemDeleted of DBCINotDeleted -> Nothing - _ -> Just (CIDeleted @CTLocal deletedTs) + _ -> Just (CIDeleted @'CTLocal deletedTs) itemEdited' = fromMaybe False itemEdited itemForwarded = toCIForwardedFrom forwardedFromRow in mkCIMeta itemId content itemText status sentViaProxy sharedMsgId itemForwarded itemDeleted' itemEdited' ciTimed itemLive currentTs itemTs Nothing createdAt updatedAt @@ -1458,7 +1476,7 @@ toDirectChatItem currentTs (((itemId, itemTs, AMsgDirection msgDir, itemContentT ciMeta content status = let itemDeleted' = case itemDeleted of DBCINotDeleted -> Nothing - _ -> Just (CIDeleted @CTDirect deletedTs) + _ -> Just (CIDeleted @'CTDirect deletedTs) itemEdited' = fromMaybe False itemEdited itemForwarded = toCIForwardedFrom forwardedFromRow in mkCIMeta itemId content itemText status sentViaProxy sharedMsgId itemForwarded itemDeleted' itemEdited' ciTimed itemLive currentTs itemTs Nothing createdAt updatedAt @@ -1520,7 +1538,7 @@ toGroupChatItem currentTs userContactId (((itemId, itemTs, AMsgDirection msgDir, DBCINotDeleted -> Nothing DBCIBlocked -> Just (CIBlocked deletedTs) DBCIBlockedByAdmin -> Just (CIBlockedByAdmin deletedTs) - _ -> Just (maybe (CIDeleted @CTGroup deletedTs) (CIModerated deletedTs) deletedByGroupMember_) + _ -> Just (maybe (CIDeleted @'CTGroup deletedTs) (CIModerated deletedTs) deletedByGroupMember_) itemEdited' = fromMaybe False itemEdited itemForwarded = toCIForwardedFrom forwardedFromRow in mkCIMeta itemId content itemText status sentViaProxy sharedMsgId itemForwarded itemDeleted' itemEdited' ciTimed itemLive currentTs itemTs forwardedByMember createdAt updatedAt @@ -1919,7 +1937,7 @@ markGroupChatItemDeleted db User {userId} GroupInfo {groupId} ci@ChatItem {meta} let itemId = chatItemId' ci (deletedByGroupMemberId, itemDeleted) = case byGroupMember_ of Just m@GroupMember {groupMemberId} -> (Just groupMemberId, Just $ CIModerated (Just deletedTs) m) - _ -> (Nothing, Just $ CIDeleted @CTGroup (Just deletedTs)) + _ -> (Nothing, Just $ CIDeleted @'CTGroup (Just deletedTs)) insertChatItemMessage_ db itemId msgId currentTs DB.execute db diff --git a/src/Simplex/Chat/Store/Migrations.hs b/src/Simplex/Chat/Store/Migrations.hs index a79a31f75d..5c9082b361 100644 --- a/src/Simplex/Chat/Store/Migrations.hs +++ b/src/Simplex/Chat/Store/Migrations.hs @@ -109,6 +109,7 @@ import Simplex.Chat.Migrations.M20240430_ui_theme import Simplex.Chat.Migrations.M20240501_chat_deleted import Simplex.Chat.Migrations.M20240510_chat_items_via_proxy import Simplex.Chat.Migrations.M20240515_rcv_files_user_approved_relays +import Simplex.Chat.Migrations.M20240528_quota_err_counter import Simplex.Messaging.Agent.Store.SQLite.Migrations (Migration (..)) schemaMigrations :: [(String, Query, Maybe Query)] @@ -217,7 +218,8 @@ schemaMigrations = ("20240430_ui_theme", m20240430_ui_theme, Just down_m20240430_ui_theme), ("20240501_chat_deleted", m20240501_chat_deleted, Just down_m20240501_chat_deleted), ("20240510_chat_items_via_proxy", m20240510_chat_items_via_proxy, Just down_m20240510_chat_items_via_proxy), - ("20240515_rcv_files_user_approved_relays", m20240515_rcv_files_user_approved_relays, Just down_m20240515_rcv_files_user_approved_relays) + ("20240515_rcv_files_user_approved_relays", m20240515_rcv_files_user_approved_relays, Just down_m20240515_rcv_files_user_approved_relays), + ("20240528_quota_err_counter", m20240528_quota_err_counter, Just down_m20240528_quota_err_counter) ] -- | 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 9b83e7299f..be06ea8878 100644 --- a/src/Simplex/Chat/Store/Profiles.hs +++ b/src/Simplex/Chat/Store/Profiles.hs @@ -342,7 +342,7 @@ getUserAddressConnections db vr User {userId} = do [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.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + 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.quota_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 @@ -358,7 +358,7 @@ getUserContactLinks db vr User {userId} = [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.pq_support, c.pq_encryption, c.pq_snd_enabled, c.pq_rcv_enabled, c.auth_err_counter, + 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.quota_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 diff --git a/src/Simplex/Chat/Store/Shared.hs b/src/Simplex/Chat/Store/Shared.hs index 113a84966d..c364cf10c2 100644 --- a/src/Simplex/Chat/Store/Shared.hs +++ b/src/Simplex/Chat/Store/Shared.hs @@ -163,12 +163,12 @@ 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, PQSupport, PQEncryption, Maybe PQEncryption, Maybe PQEncryption, Int, Maybe VersionChat, VersionChat, VersionChat) +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, 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 PQSupport, Maybe PQEncryption, Maybe PQEncryption, Maybe PQEncryption, Maybe Int, Maybe VersionChat, Maybe VersionChat, Maybe 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 PQSupport, Maybe PQEncryption, Maybe PQEncryption, Maybe PQEncryption, Maybe Int, Maybe Int, Maybe VersionChat, Maybe VersionChat, Maybe VersionChat) toConnection :: 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)) = +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, quotaErrCounter, chatV, minVer, maxVer)) = Connection { connId, agentConnId = AgentConnId acId, @@ -191,6 +191,7 @@ toConnection vr ((connId, acId, connLevel, viaContact, viaUserContactLink, viaGr pqSndEnabled, pqRcvEnabled, authErrCounter, + quotaErrCounter, createdAt } where @@ -203,8 +204,8 @@ toConnection vr ((connId, acId, connLevel, viaContact, viaUserContactLink, viaGr entityId_ ConnUserContact = userContactLinkId toMaybeConnection :: 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 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, Just quotaErrCounter, 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, quotaErrCounter, connChatVersion, minVer, maxVer)) toMaybeConnection _ _ = Nothing createConnection_ :: DB.Connection -> UserId -> ConnType -> Maybe Int64 -> ConnId -> VersionChat -> VersionRangeChat -> Maybe ContactId -> Maybe Int64 -> Maybe ProfileId -> Int -> UTCTime -> SubscriptionMode -> PQSupport -> IO Connection @@ -249,7 +250,8 @@ createConnection_ db userId connType entityId acId connChatVersion peerChatVRang pqEncryption = CR.pqSupportToEnc pqSup, pqSndEnabled = Nothing, pqRcvEnabled = Nothing, - authErrCounter = 0 + authErrCounter = 0, + quotaErrCounter = 0 } where ent ct = if connType == ct then entityId else Nothing diff --git a/src/Simplex/Chat/Terminal/Main.hs b/src/Simplex/Chat/Terminal/Main.hs index 2b26bb1d66..3e7d933669 100644 --- a/src/Simplex/Chat/Terminal/Main.hs +++ b/src/Simplex/Chat/Terminal/Main.hs @@ -6,15 +6,16 @@ module Simplex.Chat.Terminal.Main where import Control.Concurrent (forkIO, threadDelay) import Control.Concurrent.STM import Control.Monad +import Data.Maybe (fromMaybe) import Data.Time.Clock (getCurrentTime) import Data.Time.LocalTime (getCurrentTimeZone) import Network.Socket -import Simplex.Chat.Controller (ChatConfig, ChatController (..), ChatResponse (..), currentRemoteHost, versionNumber, versionString) +import Simplex.Chat.Controller (ChatConfig, ChatController (..), ChatResponse (..), SimpleNetCfg (..), currentRemoteHost, versionNumber, versionString) import Simplex.Chat.Core import Simplex.Chat.Options import Simplex.Chat.Terminal -import Simplex.Chat.View (serializeChatResponse) -import Simplex.Messaging.Client (NetworkConfig (..)) +import Simplex.Chat.View (serializeChatResponse, smpProxyModeStr) +import Simplex.Messaging.Client (NetworkConfig (..), defaultNetworkConfig) import System.Directory (getAppUserDataDirectory) import System.Exit (exitFailure) import System.Terminal (withTerminal) @@ -51,7 +52,7 @@ simplexChatCLI cfg server_ = do putStrLn $ serializeChatResponse (rh, Just user) ts tz rh r welcome :: ChatOpts -> IO () -welcome ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, networkConfig}} = +welcome ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, simpleNetCfg = SimpleNetCfg {socksProxy, smpProxyMode_, smpProxyFallback_}}} = mapM_ putStrLn [ versionString versionNumber, @@ -59,6 +60,9 @@ welcome ChatOpts {coreOptions = CoreChatOpts {dbFilePrefix, networkConfig}} = maybe "direct network connection - use `/network` command or `-x` CLI option to connect via SOCKS5 at :9050" (("using SOCKS5 proxy " <>) . show) - (socksProxy networkConfig), + socksProxy, + smpProxyModeStr + (fromMaybe (smpProxyMode defaultNetworkConfig) smpProxyMode_) + (fromMaybe (smpProxyFallback defaultNetworkConfig) smpProxyFallback_), "type \"/help\" or \"/h\" for usage info" ] diff --git a/src/Simplex/Chat/Types.hs b/src/Simplex/Chat/Types.hs index ca6cd2e375..047a9c68f7 100644 --- a/src/Simplex/Chat/Types.hs +++ b/src/Simplex/Chat/Types.hs @@ -1303,6 +1303,7 @@ data Connection = Connection pqSndEnabled :: Maybe PQEncryption, pqRcvEnabled :: Maybe PQEncryption, authErrCounter :: Int, + quotaErrCounter :: Int, -- if exceeds limit messages to group members are created as pending; sending to contacts is unaffected by this createdAt :: UTCTime } deriving (Eq, Show) @@ -1316,6 +1317,15 @@ authErrDisableCount = 10 connDisabled :: Connection -> Bool connDisabled Connection {authErrCounter} = authErrCounter >= authErrDisableCount +quotaErrInactiveCount :: Int +quotaErrInactiveCount = 5 + +quotaErrSetOnMERR :: Int +quotaErrSetOnMERR = 999 + +connInactive :: Connection -> Bool +connInactive Connection {quotaErrCounter} = quotaErrCounter >= quotaErrInactiveCount + data SecurityCode = SecurityCode {securityCode :: Text, verifiedAt :: UTCTime} deriving (Eq, Show) diff --git a/src/Simplex/Chat/Types/UITheme.hs b/src/Simplex/Chat/Types/UITheme.hs index ed92caea0e..ef445c5a7c 100644 --- a/src/Simplex/Chat/Types/UITheme.hs +++ b/src/Simplex/Chat/Types/UITheme.hs @@ -128,13 +128,16 @@ data UIColors = UIColors background :: Maybe UIColor, menus :: Maybe UIColor, title :: Maybe UIColor, + accentVariant2 :: Maybe UIColor, sentMessage :: Maybe UIColor, - receivedMessage :: Maybe UIColor + sentReply :: Maybe UIColor, + receivedMessage :: Maybe UIColor, + receivedReply :: Maybe UIColor } deriving (Eq, Show) defaultUIColors :: UIColors -defaultUIColors = UIColors Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing +defaultUIColors = UIColors Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing Nothing newtype UIColor = UIColor String deriving (Eq, Show) diff --git a/src/Simplex/Chat/Util.hs b/src/Simplex/Chat/Util.hs index 2b2bd599ae..3f7d19fd6d 100644 --- a/src/Simplex/Chat/Util.hs +++ b/src/Simplex/Chat/Util.hs @@ -1,10 +1,18 @@ +{-# LANGUAGE FlexibleInstances #-} +{-# LANGUAGE InstanceSigs #-} +{-# LANGUAGE RankNTypes #-} {-# LANGUAGE TupleSections #-} +{-# OPTIONS_GHC -Wno-orphans #-} module Simplex.Chat.Util (week, encryptFile, chunkSize, liftIOEither, shuffle) where +import Control.Exception (Exception) import Control.Monad import Control.Monad.Except import Control.Monad.IO.Class +import Control.Monad.IO.Unlift (MonadUnliftIO (..)) +import Control.Monad.Reader +import Data.Bifunctor (first) import qualified Data.ByteString.Lazy as LB import Data.List (sortBy) import Data.Ord (comparing) @@ -13,6 +21,7 @@ import Data.Word (Word16) import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.File as CF import System.Random (randomRIO) +import qualified UnliftIO.Exception as E import UnliftIO.IO (IOMode (..), withFile) week :: NominalDiffTime @@ -46,3 +55,24 @@ shuffle xs = map snd . sortBy (comparing fst) <$> mapM (\x -> (,x) <$> random) x liftIOEither :: (MonadIO m, MonadError e m) => IO (Either e a) -> m a liftIOEither a = liftIO a >>= liftEither {-# INLINE liftIOEither #-} + +newtype InternalException e = InternalException {unInternalException :: e} + deriving (Eq, Show) + +instance Exception e => Exception (InternalException e) + +instance Exception e => MonadUnliftIO (ExceptT e IO) where + {-# INLINE withRunInIO #-} + withRunInIO :: ((forall a. ExceptT e IO a -> IO a) -> IO b) -> ExceptT e IO b + withRunInIO inner = + ExceptT . fmap (first unInternalException) . E.try $ + withRunInIO $ \run -> + inner $ run . (either (E.throwIO . InternalException) pure <=< runExceptT) + +instance Exception e => MonadUnliftIO (ExceptT e (ReaderT r IO)) where + {-# INLINE withRunInIO #-} + withRunInIO :: ((forall a. ExceptT e (ReaderT r IO) a -> IO a) -> IO b) -> ExceptT e (ReaderT r IO) b + withRunInIO inner = + withExceptT unInternalException . ExceptT . E.try $ + withRunInIO $ \run -> + inner $ run . (either (E.throwIO . InternalException) pure <=< runExceptT) diff --git a/src/Simplex/Chat/View.hs b/src/Simplex/Chat/View.hs index 1c2b0b2cc1..6c02eac991 100644 --- a/src/Simplex/Chat/View.hs +++ b/src/Simplex/Chat/View.hs @@ -13,7 +13,6 @@ module Simplex.Chat.View where import qualified Data.Aeson as J import qualified Data.Aeson.TH as JQ -import Data.ByteString.Char8 (ByteString) import qualified Data.ByteString.Char8 as B import qualified Data.ByteString.Lazy.Char8 as LB import Data.Char (isSpace, toUpper) @@ -56,6 +55,7 @@ import Simplex.Messaging.Agent.Client (ProtocolTestFailure (..), ProtocolTestSte import Simplex.Messaging.Agent.Env.SQLite (NetworkConfig (..)) import Simplex.Messaging.Agent.Protocol import Simplex.Messaging.Agent.Store.SQLite.DB (SlowQueryStats (..)) +import Simplex.Messaging.Client (SMPProxyMode (..), SMPProxyFallback) import qualified Simplex.Messaging.Crypto as C import Simplex.Messaging.Crypto.File (CryptoFile (..), CryptoFileArgs (..)) import qualified Simplex.Messaging.Crypto.Ratchet as CR @@ -65,7 +65,7 @@ import Simplex.Messaging.Parsers (dropPrefix, taggedObjectJSON) import Simplex.Messaging.Protocol (AProtoServerWithAuth (..), AProtocolType, ProtoServerWithAuth, ProtocolServer (..), ProtocolTypeI, SProtocolType (..)) import qualified Simplex.Messaging.Protocol as SMP import Simplex.Messaging.Transport.Client (TransportHost (..)) -import Simplex.Messaging.Util (bshow, tshow) +import Simplex.Messaging.Util (bshow, safeDecodeUtf8, tshow) import Simplex.Messaging.Version hiding (version) import Simplex.RemoteControl.Types (RCCtrlAddress (..)) import System.Console.ANSI.Types @@ -90,10 +90,10 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRChatRunning -> ["chat is running"] CRChatStopped -> ["chat stopped"] CRChatSuspended -> ["chat suspended"] - CRApiChats u chats -> ttyUser u $ if testView then testViewChats chats else [plain . bshow $ J.encode chats] + CRApiChats u chats -> ttyUser u $ if testView then testViewChats chats else [viewJSON chats] CRChats chats -> viewChats ts tz chats - CRApiChat u chat -> ttyUser u $ if testView then testViewChat chat else [plain . bshow $ J.encode chat] - CRApiParsedMarkdown ft -> [plain . bshow $ J.encode ft] + CRApiChat u chat -> ttyUser u $ if testView then testViewChat chat else [viewJSON chat] + CRApiParsedMarkdown ft -> [viewJSON ft] CRUserProtoServers u userServers -> ttyUser u $ viewUserServers userServers testView CRServerTestResult u srv testFailure -> ttyUser u $ viewServerTestResult srv testFailure CRChatItemTTL u ttl -> ttyUser u $ viewChatItemTTL ttl @@ -101,6 +101,10 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRContactInfo u ct cStats customUserProfile -> ttyUser u $ viewContactInfo ct cStats customUserProfile CRGroupInfo u g s -> ttyUser u $ viewGroupInfo g s CRGroupMemberInfo u g m cStats -> ttyUser u $ viewGroupMemberInfo g m cStats + CRQueueInfo _ msgInfo qInfo -> + [ "last received msg: " <> maybe "none" viewJSON msgInfo, + "server queue info: " <> viewJSON qInfo + ] CRContactSwitchStarted {} -> ["switch started"] CRGroupMemberSwitchStarted {} -> ["switch started"] CRContactSwitchAborted {} -> ["switch aborted"] @@ -220,7 +224,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe 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_ + CRStandaloneFileInfo info_ -> maybe ["no file information in URI"] (\j -> [viewJSON j]) info_ CRContactConnecting u _ -> ttyUser u [] CRContactConnected u ct userCustomProfile -> ttyUser u $ viewContactConnected ct userCustomProfile testView CRContactAnotherClient u c -> ttyUser u [ttyContact' c <> ": contact is connected to another client"] @@ -322,7 +326,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe ] CRRemoteFileStored rhId (CryptoFile filePath cfArgs_) -> [plain $ "file " <> filePath <> " stored on remote host " <> show rhId] - <> maybe [] ((: []) . plain . cryptoFileArgsStr testView) cfArgs_ + <> maybe [] ((: []) . cryptoFileArgsStr testView) cfArgs_ CRRemoteCtrlList cs -> viewRemoteCtrls cs CRRemoteCtrlFound {remoteCtrl = RemoteCtrlInfo {remoteCtrlId, ctrlDeviceName}, ctrlAppInfo_, appVersion, compatible} -> [ ("remote controller " <> sShow remoteCtrlId <> " found: ") @@ -354,8 +358,8 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe in ("Chat queries" : map viewQuery chatQueries) <> [""] <> ("Agent queries" : map viewQuery agentQueries) CRDebugLocks {chatLockName, chatEntityLocks, agentLocks} -> [ maybe "no chat lock" (("chat lock: " <>) . plain) chatLockName, - plain $ "chat entity locks: " <> LB.unpack (J.encode chatEntityLocks), - plain $ "agent locks: " <> LB.unpack (J.encode agentLocks) + "chat entity locks: " <> viewJSON chatEntityLocks, + "agent locks: " <> viewJSON agentLocks ] CRAgentStats stats -> map (plain . intercalate ",") stats CRAgentSubs {activeSubs, pendingSubs, removedSubs} -> @@ -370,14 +374,19 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe ("active subscriptions:" : map sShow activeSubscriptions) <> ("pending subscriptions: " : map sShow pendingSubscriptions) <> ("removed subscriptions: " : map sShow removedSubscriptions) - CRAgentWorkersSummary {agentWorkersSummary} -> ["agent workers summary: " <> plain (LB.unpack $ J.encode agentWorkersSummary)] + CRAgentWorkersSummary {agentWorkersSummary} -> ["agent workers summary: " <> viewJSON agentWorkersSummary] CRAgentWorkersDetails {agentWorkersDetails} -> [ "agent workers details:", - plain . LB.unpack $ J.encode agentWorkersDetails -- this would be huge, but copypastable when has its own line + viewJSON agentWorkersDetails -- this would be huge, but copypastable when has its own line + ] + CRAgentMsgCounts {msgCounts} -> ["received messages (total, duplicates):", viewJSON msgCounts] + CRAgentQueuesInfo {agentQueuesInfo} -> + [ "agent queues info:", + plain . LB.unpack $ J.encode agentQueuesInfo ] - CRAgentMsgCounts {msgCounts} -> ["received messages (total, duplicates):", plain . LB.unpack $ J.encode msgCounts] CRContactDisabled u c -> ttyUser u ["[" <> ttyContact' c <> "] connection is disabled, to enable: " <> highlight ("/enable " <> viewContactName c) <> ", to delete: " <> highlight ("/d " <> viewContactName c)] CRConnectionDisabled entity -> viewConnectionEntityDisabled entity + CRConnectionInactive entity inactive -> viewConnectionEntityInactive entity inactive CRAgentRcvQueueDeleted acId srv aqId err_ -> [ ("completed deleting rcv queue, agent connection id: " <> sShow acId) <> (", server: " <> sShow srv) @@ -388,11 +397,11 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe CRAgentConnDeleted acId -> ["completed deleting connection, agent connection id: " <> sShow acId | logLevel <= CLLInfo] CRAgentUserDeleted auId -> ["completed deleting user" <> if logLevel <= CLLInfo then ", agent user id: " <> sShow auId else ""] CRMessageError u prefix err -> ttyUser u [plain prefix <> ": " <> plain err | prefix == "error" || logLevel <= CLLWarning] - CRChatCmdError u e -> ttyUserPrefix' u $ viewChatError logLevel testView e - CRChatError u e -> ttyUser' u $ viewChatError logLevel testView e - CRChatErrors u errs -> ttyUser' u $ concatMap (viewChatError logLevel testView) errs + CRChatCmdError u e -> ttyUserPrefix' u $ viewChatError True logLevel testView e + CRChatError u e -> ttyUser' u $ viewChatError False logLevel testView e + CRChatErrors u errs -> ttyUser' u $ concatMap (viewChatError False logLevel testView) errs CRArchiveImported archiveErrs -> if null archiveErrs then ["ok"] else ["archive import errors: " <> plain (show archiveErrs)] - CRAppSettings as -> ["app settings: " <> plain (LB.unpack $ J.encode as)] + CRAppSettings as -> ["app settings: " <> viewJSON as] CRTimedAction _ _ -> [] CRCustomChatResponse u r -> ttyUser' u $ [plain r] where @@ -1205,12 +1214,17 @@ viewChatItemTTL = \case deletedAfter ttlStr = ["old messages are set to be deleted after: " <> ttlStr] viewNetworkConfig :: NetworkConfig -> [StyledString] -viewNetworkConfig NetworkConfig {socksProxy, tcpTimeout} = +viewNetworkConfig NetworkConfig {socksProxy, tcpTimeout, smpProxyMode, smpProxyFallback} = [ plain $ maybe "direct network connection" (("using SOCKS5 proxy " <>) . show) socksProxy, "TCP timeout: " <> sShow tcpTimeout, - "use " <> highlight' "/network socks=[ timeout=]" <> " to change settings" + plain $ smpProxyModeStr smpProxyMode smpProxyFallback, + "use " <> highlight' "/network socks=[ timeout=][ smp-proxy=always/unknown/unprotected/never][ smp-proxy-fallback=no/protected/yes]" <> " to change settings" ] +smpProxyModeStr :: SMPProxyMode -> SMPProxyFallback -> String +smpProxyModeStr SPMNever _ = "private message routing disabled." +smpProxyModeStr mode fallback = T.unpack $ safeDecodeUtf8 $ "private message routing mode: " <> strEncode mode <> ", fallback: " <> strEncode fallback + viewContactInfo :: Contact -> Maybe ConnectionStats -> Maybe Profile -> [StyledString] viewContactInfo ct@Contact {contactId, profile = LocalProfile {localAlias, contactLink}, activeConn, uiThemes, customData} stats incognitoProfile = ["contact ID: " <> sShow contactId] @@ -1236,10 +1250,10 @@ viewGroupInfo GroupInfo {groupId, uiThemes, customData} s = <> viewCustomData customData viewUITheme :: Maybe UIThemeEntityOverrides -> [StyledString] -viewUITheme = maybe [] (\uiThemes -> ["UI themes: " <> plain (LB.toStrict $ J.encode uiThemes)]) +viewUITheme = maybe [] (\uiThemes -> ["UI themes: " <> viewJSON uiThemes]) viewCustomData :: Maybe CustomData -> [StyledString] -viewCustomData = maybe [] (\(CustomData v) -> ["custom data: " <> plain (LB.toStrict . J.encode $ J.Object v)]) +viewCustomData = maybe [] (\(CustomData v) -> ["custom data: " <> viewJSON (J.Object v)]) viewGroupMemberInfo :: GroupInfo -> GroupMember -> Maybe ConnectionStats -> [StyledString] viewGroupMemberInfo GroupInfo {groupId} m@GroupMember {groupMemberId, memberProfile = LocalProfile {localAlias, contactLink}, activeConn} stats = @@ -1681,7 +1695,7 @@ receivingFile_' :: (Maybe RemoteHostId, Maybe User) -> Bool -> String -> AChatIt receivingFile_' hu testView status (AChatItem _ _ chat ChatItem {file = Just CIFile {fileId, fileName, fileSource = Just f@(CryptoFile _ cfArgs_)}, chatDir}) = [plain status <> " receiving " <> fileTransferStr fileId fileName <> fileFrom chat chatDir] <> cfArgsStr cfArgs_ <> getRemoteFileStr where - cfArgsStr (Just cfArgs) = [plain (cryptoFileArgsStr testView cfArgs) | status == "completed"] + cfArgsStr (Just cfArgs) = [cryptoFileArgsStr testView cfArgs | status == "completed"] cfArgsStr _ = [] getRemoteFileStr = case hu of (Just rhId, Just User {userId}) @@ -1702,10 +1716,10 @@ viewLocalFile to CIFile {fileId, fileSource} ts tz = case fileSource of Just (CryptoFile fPath _) -> sentWithTime_ ts tz [to <> fileTransferStr fileId fPath] _ -> const [] -cryptoFileArgsStr :: Bool -> CryptoFileArgs -> ByteString +cryptoFileArgsStr :: Bool -> CryptoFileArgs -> StyledString cryptoFileArgsStr testView cfArgs@(CFArgs key nonce) - | testView = LB.toStrict $ J.encode cfArgs - | otherwise = "encryption key: " <> strEncode key <> ", nonce: " <> strEncode nonce + | testView = viewJSON cfArgs + | otherwise = plain $ "encryption key: " <> strEncode key <> ", nonce: " <> strEncode nonce fileFrom :: ChatInfo c -> CIDirection c d -> StyledString fileFrom (DirectChat ct) CIDirectRcv = " from " <> ttyContact' ct @@ -1823,7 +1837,7 @@ viewCallAnswer ct WebRTCSession {rtcSession = answer, rtcIceCandidates = iceCand [ ttyContact' ct <> " continued the WebRTC call", "To connect, please paste the data below in your browser window you opened earlier and click Connect button", "", - plain . LB.toStrict . J.encode $ WCCallAnswer {answer, iceCandidates} + viewJSON WCCallAnswer {answer, iceCandidates} ] callMediaStr :: CallType -> StyledString @@ -1894,8 +1908,8 @@ viewRemoteCtrl CtrlAppInfo {deviceName, appVersionRange = AppVersionRange _ (App | otherwise = "" showCompatible = if compatible then "" else ", " <> bold' "not compatible" -viewChatError :: ChatLogLevel -> Bool -> ChatError -> [StyledString] -viewChatError logLevel testView = \case +viewChatError :: Bool -> ChatLogLevel -> Bool -> ChatError -> [StyledString] +viewChatError isCmd logLevel testView = \case ChatError err -> case err of CENoActiveUser -> ["error: active user is required"] CENoConnectionUser agentConnId -> ["error: message user not found, conn id: " <> sShow agentConnId | logLevel <= CLLError] @@ -1953,7 +1967,6 @@ viewChatError logLevel testView = \case ] CEGroupMemberUserRemoved -> ["you are no longer a member of the group"] CEGroupMemberNotFound -> ["group doesn't have this member"] - CEGroupMemberIntroNotFound c -> ["group member intro not found for " <> ttyContact c] CEGroupCantResendInvitation g c -> viewCannotResendInvitation g c CEGroupInternal s -> ["chat group bug: " <> plain s] CEFileNotFound f -> ["file not found: " <> plain f] @@ -2028,18 +2041,20 @@ viewChatError logLevel testView = \case DBErrorOpen e -> ["error opening database after encryption: " <> sqliteError' e] e -> ["chat database error: " <> sShow e] ChatErrorAgent err entity_ -> case err of - CMD PROHIBITED -> [withConnEntity <> "error: command is prohibited"] + CMD PROHIBITED cxt -> [withConnEntity <> plain ("error: command is prohibited, " <> cxt)] SMP _ SMP.AUTH -> [ withConnEntity <> "error: connection authorization failed - this could happen if connection was deleted,\ \ secured with different credentials, or due to a bug - please re-create the connection" ] - AGENT A_DUPLICATE -> [withConnEntity <> "error: AGENT A_DUPLICATE" | logLevel == CLLDebug] - AGENT A_PROHIBITED -> [withConnEntity <> "error: AGENT A_PROHIBITED" | logLevel <= CLLWarning] - CONN NOT_FOUND -> [withConnEntity <> "error: CONN NOT_FOUND" | logLevel <= CLLWarning] + BROKER _ NETWORK | not isCmd -> [] + BROKER _ TIMEOUT | not isCmd -> [] + AGENT A_DUPLICATE -> [withConnEntity <> "error: AGENT A_DUPLICATE" | logLevel == CLLDebug || isCmd] + AGENT (A_PROHIBITED e) -> [withConnEntity <> "error: AGENT A_PROHIBITED, " <> plain e | logLevel <= CLLWarning || isCmd] + CONN NOT_FOUND -> [withConnEntity <> "error: CONN NOT_FOUND" | logLevel <= CLLWarning || isCmd] CRITICAL restart e -> [plain $ "critical error: " <> e] <> ["please restart the app" | restart] INTERNAL e -> [plain $ "internal error: " <> e] - e -> [withConnEntity <> "smp agent error: " <> sShow e | logLevel <= CLLWarning] + e -> [withConnEntity <> "smp agent error: " <> sShow e | logLevel <= CLLWarning || isCmd] where withConnEntity = case entity_ of Just entity@(RcvDirectMsgConnection conn contact_) -> case contact_ of @@ -2075,6 +2090,14 @@ viewConnectionEntityDisabled entity = case entity of where entityLabel = connEntityLabel entity +viewConnectionEntityInactive :: ConnectionEntity -> Bool -> [StyledString] +viewConnectionEntityInactive entity inactive + | inactive = ["[" <> connEntityLabel entity <> "] connection is marked as inactive"] + | otherwise = ["[" <> connEntityLabel entity <> "] inactive connection is marked as active"] + +viewJSON :: J.ToJSON a => a -> StyledString +viewJSON = plain . LB.toStrict . J.encode + connEntityLabel :: ConnectionEntity -> StyledString connEntityLabel = \case RcvDirectMsgConnection _ (Just Contact {localDisplayName = c}) -> plain c diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs index 2bcd52ab3f..589d880e8f 100644 --- a/tests/ChatClient.hs +++ b/tests/ChatClient.hs @@ -25,7 +25,7 @@ import Data.Maybe (isNothing) import qualified Data.Text as T import Network.Socket import Simplex.Chat -import Simplex.Chat.Controller (ChatCommand (..), ChatConfig (..), ChatController (..), ChatDatabase (..), ChatLogLevel (..)) +import Simplex.Chat.Controller (ChatCommand (..), ChatConfig (..), ChatController (..), ChatDatabase (..), ChatLogLevel (..), defaultSimpleNetCfg) import Simplex.Chat.Core import Simplex.Chat.Options import Simplex.Chat.Protocol (currentChatVersion, pqEncryptionCompressionVersion) @@ -44,7 +44,7 @@ import Simplex.Messaging.Agent.Protocol (currentSMPAgentVersion, duplexHandshake import Simplex.Messaging.Agent.RetryInterval 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.Client (ProtocolClientConfig (..)) import Simplex.Messaging.Client.Agent (defaultSMPClientAgentConfig) import Simplex.Messaging.Crypto.Ratchet (supportedE2EEncryptVRange) import qualified Simplex.Messaging.Crypto.Ratchet as CR @@ -94,7 +94,7 @@ testCoreOpts = -- dbKey = "this is a pass-phrase to encrypt the database", smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7001"], xftpServers = ["xftp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7002"], - networkConfig = defaultNetworkConfig, + simpleNetCfg = defaultSimpleNetCfg, logLevel = CLLImportant, logConnections = False, logServerHosts = False, @@ -401,8 +401,8 @@ testChatCfg4 cfg p1 p2 p3 p4 test = testChatN cfg testOpts [p1, p2, p3, p4] test concurrentlyN_ :: [IO a] -> IO () concurrentlyN_ = mapConcurrently_ id -serverCfg :: ServerConfig -serverCfg = +smpServerCfg :: ServerConfig +smpServerCfg = ServerConfig { transports = [(serverPort, transport @TLS)], tbqSize = 1, @@ -431,11 +431,16 @@ serverCfg = smpHandshakeTimeout = 1000000, controlPort = Nothing, smpAgentCfg = defaultSMPClientAgentConfig, - allowSMPProxy = False + allowSMPProxy = False, + serverClientConcurrency = 16, + information = Nothing } withSmpServer :: IO () -> IO () -withSmpServer = serverBracket (`runSMPServerBlocking` serverCfg) +withSmpServer = withSmpServer' smpServerCfg + +withSmpServer' :: ServerConfig -> IO () -> IO () +withSmpServer' cfg = serverBracket (`runSMPServerBlocking` cfg) xftpTestPort :: ServiceName xftpTestPort = "7002" @@ -468,7 +473,8 @@ xftpServerConfig = serverStatsLogFile = "tests/tmp/xftp-server-stats.daily.log", serverStatsBackupFile = Nothing, controlPort = Nothing, - transportConfig = defaultTransportServerConfig + transportConfig = defaultTransportServerConfig, + responseDelay = 0 } withXFTPServer :: IO () -> IO () diff --git a/tests/ChatTests/Direct.hs b/tests/ChatTests/Direct.hs index 2e549d9dd1..24281ae830 100644 --- a/tests/ChatTests/Direct.hs +++ b/tests/ChatTests/Direct.hs @@ -2310,12 +2310,12 @@ testAbortSwitchContact tmp = do alice <## "bob: you started changing address" -- repeat switch is prohibited alice ##> "/switch bob" - alice <## "error: command is prohibited" + alice <## "error: command is prohibited, switchConnectionAsync: already switching" -- stop switch alice #$> ("/abort switch bob", id, "switch aborted") -- repeat switch stop is prohibited alice ##> "/abort switch bob" - alice <## "error: command is prohibited" + alice <## "error: command is prohibited, abortConnectionSwitch: not allowed" withTestChatContactConnected tmp "bob" $ \bob -> do bob <## "alice started changing address for you" -- alice changes address again @@ -2356,12 +2356,12 @@ testAbortSwitchGroupMember tmp = do alice <## "#team: you started changing address for bob" -- repeat switch is prohibited alice ##> "/switch #team bob" - alice <## "error: command is prohibited" + alice <## "error: command is prohibited, switchConnectionAsync: already switching" -- stop switch alice #$> ("/abort switch #team bob", id, "switch aborted") -- repeat switch stop is prohibited alice ##> "/abort switch #team bob" - alice <## "error: command is prohibited" + alice <## "error: command is prohibited, abortConnectionSwitch: not allowed" withTestChatContactConnected tmp "bob" $ \bob -> do bob <## "#team: connected to server(s)" bob <## "#team: alice started changing address for you" @@ -2485,7 +2485,7 @@ setupDesynchronizedRatchet tmp alice = do withTestChat tmp "bob_old" $ \bob -> do bob <## "1 contacts connected (use /cs for the list)" bob ##> "/sync alice" - bob <## "error: command is prohibited" + bob <## "error: command is prohibited, synchronizeRatchet: not allowed" alice #> "@bob 1" bob <## "alice: decryption error (connection out of sync), synchronization required" bob <## "use /sync alice to synchronize" @@ -2495,7 +2495,7 @@ setupDesynchronizedRatchet tmp alice = do bob ##> "/tail @alice 1" bob <# "alice> decryption error, possibly due to the device change (header, 3 messages)" bob ##> "@alice 1" - bob <## "error: command is prohibited" + bob <## "error: command is prohibited, sendMessagesB: send prohibited" (alice )) import Test.Hspec hiding (it) @@ -150,6 +158,8 @@ chatGroupTests = do it "another admin can unblock" testBlockForAllAnotherAdminUnblocks it "member was blocked before joining group" testBlockForAllBeforeJoining it "can't repeat block, unblock" testBlockForAllCantRepeat + describe "group member inactivity" $ do + it "mark member inactive on reaching quota" testGroupMemberInactive where _0 = supportedChatVRange -- don't create direct connections _1 = groupCreateDirectVRange @@ -3266,7 +3276,7 @@ setupDesynchronizedRatchet tmp alice = do bob <## "1 contacts connected (use /cs for the list)" bob <## "#team: connected to server(s)" bob ##> "/sync #team alice" - bob <## "error: command is prohibited" + bob <## "error: command is prohibited, synchronizeRatchet: not allowed" alice #> "#team 1" bob <## "#team alice: decryption error (connection out of sync), synchronization required" bob <## "use /sync #team alice to synchronize" @@ -3294,7 +3304,7 @@ testGroupSyncRatchet tmp = bob <## "1 contacts connected (use /cs for the list)" bob <## "#team: connected to server(s)" bob `send` "#team 1" - bob <## "error: command is prohibited" -- silence? + bob <## "error: command is prohibited, sendMessagesB: send prohibited" -- silence? bob <# "#team 1" (alice 3" bob #$> ("/_get chat #1 count=3", chat, [(1, "1"), (1, "2"), (1, "3")]) + +testGroupMemberInactive :: HasCallStack => FilePath -> IO () +testGroupMemberInactive tmp = do + withSmpServer' serverCfg' $ do + withNewTestChatCfgOpts tmp cfg' opts' "alice" aliceProfile $ \alice -> do + withNewTestChatCfgOpts tmp cfg' opts' "bob" bobProfile $ \bob -> do + createGroup2 "team" alice bob + + alice #> "#team hi" + bob <# "#team alice> hi" + bob #> "#team hey" + alice <# "#team bob> hey" + + -- bob is offline + alice #> "#team 1" + alice #> "#team 2" + alice #> "#team 3" + alice <## "[#team bob] connection is marked as inactive" + -- 4 and 5 will be sent to bob as pending messages + alice #> "#team 4" + alice #> "#team 5" + + pgmCount <- withCCTransaction alice $ \db -> + DB.query_ db "SELECT count(1) FROM pending_group_messages" :: IO [[Int]] + pgmCount `shouldBe` [[2]] + + threadDelay 1500000 + + withTestChatCfgOpts tmp cfg' opts' "bob" $ \bob -> do + bob <## "1 contacts connected (use /cs for the list)" + bob <## "#team: connected to server(s)" + bob <# "#team alice> 1" + bob <# "#team alice> 2" + bob <#. "#team alice> skipped message ID" + alice <## "[#team bob] inactive connection is marked as active" + + bob <# "#team alice> 4" + bob <# "#team alice> 5" + + pgmCount' <- withCCTransaction alice $ \db -> + DB.query_ db "SELECT count(1) FROM pending_group_messages" :: IO [[Int]] + pgmCount' `shouldBe` [[0]] + + -- delivery works + alice #> "#team hi" + bob <# "#team alice> hi" + bob #> "#team hey" + alice <# "#team bob> hey" + where + serverCfg' = + smpServerCfg + { transports = [("7003", transport @TLS)], + msgQueueQuota = 2 + } + fastRetryInterval = defaultReconnectInterval {initialInterval = 50_000} -- same as in agent tests + cfg' = + testCfg + { agentConfig = + testAgentCfg + { quotaExceededTimeout = 1, + messageRetryInterval = RetryInterval2 {riFast = fastRetryInterval, riSlow = fastRetryInterval} + } + } + opts' = + testOpts + { coreOptions = + testCoreOpts + { smpServers = ["smp://LcJUMfVhwD8yxjAiSaDzzGF3-kLG4Uh0Fl_ZIjrRwjI=:server_password@localhost:7003"] + } + } diff --git a/tests/MobileTests.hs b/tests/MobileTests.hs index f41d0172de..cd4b3980eb 100644 --- a/tests/MobileTests.hs +++ b/tests/MobileTests.hs @@ -294,7 +294,7 @@ testFileCApi fileName tmp = do let sz' = fromIntegral sz contents <- create sz' $ \toPtr -> copyBytes toPtr (ptr' `plusPtr` 5) sz' contents `shouldBe` src - sz' `shouldBe` fromIntegral len + sz' `shouldBe` len testMissingFileCApi :: FilePath -> IO () testMissingFileCApi tmp = do diff --git a/website/langs/ar.json b/website/langs/ar.json index 7b087aab42..3551ffd31f 100644 --- a/website/langs/ar.json +++ b/website/langs/ar.json @@ -240,7 +240,7 @@ "signing-key-fingerprint": "توقيع مفتاح البصمة (SHA-256)", "f-droid-org-repo": "مستودع F-Droid.org", "stable-versions-built-by-f-droid-org": "الإصدارات الثابتة التي تم إنشاؤها بواسطة F-Droid.org", - "releases-to-this-repo-are-done-1-2-days-later": "يتم إصدار الإصدارات إلى هذا المستودع بعد يوم أو يومين", + "releases-to-this-repo-are-done-1-2-days-later": "تتم الإصدارات إلى هذا المستودع بعد عِدة أيام", "f-droid-page-simplex-chat-repo-section-text": "لإضافته إلى عميل F-Droid، امسح رمز QR أو استخدم عنوان URL هذا:", "f-droid-page-f-droid-org-repo-section-text": "مستودعات SimpleX Chat و F-Droid.org مبنية على مفاتيح مختلفة. للتبديل، يُرجى تصدير قاعدة بيانات الدردشة وإعادة تثبيت التطبيق.", "comparison-section-list-point-4a": "مُرحلات SimpleX لا يمكنها أن تتنازل عن تعمية بين الطرفين. تحقق من رمز الأمان للتخفيف من الهجوم على القناة خارج النطاق", diff --git a/website/langs/de.json b/website/langs/de.json index c6bcc9920f..0972423887 100644 --- a/website/langs/de.json +++ b/website/langs/de.json @@ -237,7 +237,7 @@ "f-droid-org-repo": "F-Droid.org Repository", "stable-versions-built-by-f-droid-org": "Von F-Droid.org erstellte stabile Versionen", "f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat- und F-Droid.org-Repositorys signieren ihre Builds mit verschiedenen Schlüsseln. Zum Umschalten bitte die Chat-Datenbank exportieren und die App neu installieren.", - "releases-to-this-repo-are-done-1-2-days-later": "Die Versionen für dieses Repository werden 1..2 Tage später erstellt", + "releases-to-this-repo-are-done-1-2-days-later": "Die Versionen für dieses Repository werden einige Tage später erstellt", "docs-dropdown-8": "SimpleX Verzeichnisdienst", "simplex-chat-via-f-droid": "SimpleX Chat per F-Droid", "simplex-chat-repo": "SimpleX Chat Repository", diff --git a/website/langs/hu.json b/website/langs/hu.json new file mode 100644 index 0000000000..1c860fa023 --- /dev/null +++ b/website/langs/hu.json @@ -0,0 +1,259 @@ +{ + "home": "Főoldal", + "developers": "Fejlesztők", + "reference": "Referencia", + "blog": "Blog", + "features": "Funkciók", + "why-simplex": "Miért válassza a SimpleX-t", + "simplex-privacy": "SimpleX adatvédelem", + "simplex-network": "SimpleX hálózat", + "simplex-explained": "Simplex bemutatása", + "simplex-explained-tab-1-text": "1. Felhasználói élmény", + "simplex-explained-tab-2-text": "2. Hogyan működik", + "simplex-explained-tab-3-text": "3. Mit látnak a kiszolgálók", + "simplex-explained-tab-1-p-1": "Létrehozhat kapcsolatokat és csoportokat, valamint kétirányú beszélgetéseket folytathat, mint bármely más üzenetküldőben.", + "simplex-explained-tab-1-p-2": "Hogyan működhet egyirányú üzenet várakoztatással és felhasználói profil azonosítók nélkül?", + "simplex-explained-tab-2-p-1": "Minden kapcsolathoz két különböző üzenetküldési várakoztatást használ a különböző kiszolgálókon keresztül történő üzenetküldéshez és -fogadáshoz.", + "simplex-explained-tab-2-p-2": "A kiszolgálók csak egyirányú üzeneteket továbbítanak, anélkül, hogy teljes képet kapnának a felhasználók beszélgetéseiről vagy kapcsolatairól.", + "simplex-explained-tab-3-p-1": "A kiszolgálók minden egyes üzenet várakoztatáshoz külön névtelen hitelesítő adatokkal rendelkeznek, és nem tudják, hogy melyik felhasználóhoz tartoznak.", + "simplex-explained-tab-3-p-2": "A felhasználók tovább fokozhatják a metaadatok adatvédelmét, ha a Tor segítségével férnek hozzá a kiszolgálókhoz, megakadályozva az IP-cím szerinti korrelációt.", + "smp-protocol": "SMP protokoll", + "chat-protocol": "Csevegés protokoll", + "donate": "Támogatás", + "copyright-label": "© 2020-2023 SimpleX | Nyílt forráskódú projekt", + "simplex-chat-protocol": "SimpleX Chat protokoll", + "terminal-cli": "Terminál CLI", + "terms-and-privacy-policy": "Adatvédelmi irányelvek", + "hero-header": "Újradefiniált adatvédelem", + "hero-subheader": "Az első üzenetküldő
felhasználói azonosítók nélkül", + "hero-p-1": "Más alkalmazások felhasználói azonosítókkal rendelkeznek: Signal, Matrix, Session, Briar, Jami, Cwtch, stb.
A SimpleX nem, még véletlenszerű számok sem.
Ez radikálisan javítja az adatvédelmet.", + "hero-overlay-1-textlink": "Miért ártanak a felhasználói azonosítók az adatvédelemnek?", + "hero-overlay-2-textlink": "Hogyan működik a SimpleX?", + "hero-overlay-3-textlink": "A biztonság értékelése", + "hero-2-header": "Privát kapcsolat létrehozása", + "hero-2-header-desc": "A videó bemutatja, hogyan kapcsolódhat az ismerőséhez egy egyszer használatos QR-kód segítségével, személyesen vagy videokapcsolaton keresztül. Ugyanakkor egy meghívó megosztásával is kapcsolódhat.", + "hero-overlay-1-title": "Hogyan működik a SimpleX?", + "hero-overlay-2-title": "Miért ártanak a felhasználói azonosítók az adatvédelemnek?", + "hero-overlay-3-title": "A biztonság értékelése", + "feature-1-title": "E2E-titkosított üzenetek markdown formázással és szerkesztéssel", + "feature-2-title": "E2E-titkosított
képek, videók és fájlok", + "feature-3-title": "E2E-titkosított decentralizált csoportok — csak a felhasználók tudják, hogy ezek léteznek", + "feature-4-title": "E2E-titkosított hangüzenetek", + "feature-5-title": "Eltűnő üzenetek", + "feature-6-title": "E2E-titkosított
hang- és videohívások", + "feature-7-title": "Hordozható titkosított alkalmazás-adattárolás — profil áthelyezése egy másik eszközre", + "feature-8-title": "Az inkognitó mód —
egyedülálló a SimpleX Chat-ben", + "simplex-network-overlay-1-title": "Összehasonlítás más P2P üzenetküldő protokollokkal", + "simplex-private-1-title": "2 rétegű végpontok közötti titkosítás", + "simplex-private-2-title": "További rétege a
kiszolgáló titkosítás", + "simplex-private-4-title": "Opcionális
hozzáférés Tor-on keresztül", + "simplex-private-5-title": "Több rétegű
tartalom kitöltés", + "simplex-private-6-title": "Sávon kívüli
kulcscsere", + "simplex-private-7-title": "Üzenetintegritás
hitelesítés", + "simplex-private-8-title": "Üzenetek keverése
a korreláció csökkentése érdekében", + "simplex-private-9-title": "Egyirányú
üzenet várakoztatás", + "simplex-private-10-title": "Ideiglenes névtelen páronkénti azonosítók", + "simplex-private-card-1-point-1": "Dupla-ratchet protokoll —
OTR üzenetküldés, sérülés utáni titkosság-védelemmel és -helyreállítással.", + "simplex-private-card-1-point-2": "NaCL cryptobox minden egyes üzenet várakoztatáshoz, hogy megakadályozza a forgalom korrelációját az üzenet várakoztatások között, ha a TLS veszélybe kerül.", + "simplex-private-card-2-point-1": "Kiegészítő kiszolgáló titkosítási réteg a címzettnek történő kézbesítéshez, hogy megakadályozza a fogadott és az elküldött kiszolgálóforgalom közötti korrelációt, ha a TLS veszélybe kerül.", + "simplex-private-card-3-point-1": "Az ügyfél-kiszolgáló kapcsolatokhoz csak az erős algoritmusokkal rendelkező TLS 1.2/1.3 protokollt használ.", + "simplex-private-card-3-point-2": "A kiszolgáló ujjlenyomata és a csatornakötés megakadályozza a MITM- és a visszajátszási támadásokat.", + "simplex-private-card-3-point-3": "Az újrakapcsolódás le van tiltva a munkamenet elleni támadások megelőzése érdekében.", + "simplex-private-card-4-point-1": "Az IP-címe védelme érdekében a kiszolgálókat a Tor-on vagy más átviteli fedett hálózaton keresztül érheti el.", + "simplex-private-card-6-point-1": "Számos kommunikációs platform sebezhető a kiszolgálók vagy a hálózati szolgáltatók MITM-támadásaival szemben.", + "simplex-private-card-6-point-2": "Ennek megakadályozása érdekében a SimpleX-alkalmazások egyszeri kulcsokat adnak át sávon kívül, amikor egy címet hivatkozásként vagy QR-kódként oszt meg.", + "simplex-private-card-7-point-1": "Az integritás garantálása érdekében az üzenetek sorszámozással vannak ellátva, és tartalmazzák az előző üzenet hash-ét.", + "simplex-private-card-7-point-2": "Ha bármilyen üzenetet hozzáadnak, eltávolítanak vagy módosítanak, a címzett értesítést kap róla.", + "simplex-private-card-8-point-1": "A SimpleX-kiszolgálók alacsony késleltetésű keverési csomópontokként működnek — a bejövő és kimenő üzenetek sorrendje eltérő.", + "simplex-private-card-9-point-1": "Minden üzenetet egyetlen irányba várakoztat, a különböző küldési és vételi címekkel.", + "simplex-private-card-9-point-2": "A hagyományos üzenetküldőkhöz képest csökkenti a támadási vektorokat és a rendelkezésre álló metaadatokat.", + "simplex-private-card-10-point-1": "A SimpleX ideiglenes névtelen páros címeket és hitelesítő adatokat használ minden egyes felhasználói kapcsolat vagy csoporttag számára.", + "simplex-private-card-10-point-2": "Lehetővé teszi az üzenetek felhasználói profilazonosítók nélküli kézbesítését, ami az alternatíváknál jobb metaadat-védelmet biztosít.", + "privacy-matters-1-overlay-1-title": "Az adatvédelemmel pénzt spórol meg", + "privacy-matters-1-overlay-1-linkText": "Az adatvédelemmel pénzt spórol meg", + "privacy-matters-2-title": "A választások manipulálása", + "privacy-matters-2-overlay-1-title": "Az adatvédelem hatalmat ad", + "privacy-matters-2-overlay-1-linkText": "Az adatvédelem hatalmat ad", + "privacy-matters-3-title": "Ártatlan összefüggés miatti vádemelés", + "privacy-matters-3-overlay-1-title": "Az adatvédelem szabaddá tesz", + "privacy-matters-3-overlay-1-linkText": "Az adatvédelem szabaddá tesz", + "simplex-unique-1-title": "Teljes magánéletet élvezhet", + "simplex-unique-1-overlay-1-title": "Személyazonosságának, profiljának, kapcsolatainak és metaadatainak teljes körű védelme", + "simplex-unique-2-title": "Véd
a spamektől és a visszaélésektől", + "simplex-unique-2-overlay-1-title": "A legjobb védelem a spam és a visszaélések ellen", + "simplex-unique-3-title": "Az ön adatai fölött csak ön rendelkezik", + "simplex-unique-3-overlay-1-title": "Az ön adatai fölött csak ön rendelkezik", + "simplex-unique-4-title": "Öné a SimpleX hálózat", + "simplex-unique-4-overlay-1-title": "Teljesen decentralizált — a SimpleX hálózat a felhasználóké", + "hero-overlay-card-1-p-1": "Sok felhasználó kérdezte: ha a SimpleX-nek nincsenek felhasználói azonosítói, honnan tudja, hová kell eljuttatni az üzeneteket?", + "hero-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez az összes többi platform által használt felhasználói azonosítók helyett a SimpleX az üzenetek várakoztatásához ideiglenes, névtelen, páros azonosítókat használ, külön-külön minden egyes kapcsolathoz — nincsenek hosszú távú azonosítók.", + "hero-overlay-card-1-p-4": "Ez a kialakítás megakadályozza a felhasználók metaadatainak kiszivárgását az alkalmazás szintjén. Az adatvédelem további javítása és az IP-cím védelme érdekében az üzenetküldő kiszolgálókhoz Tor hálózaton keresztül is kapcsolódhat.", + "hero-overlay-card-1-p-5": "Csak a kliensek tárolják a felhasználói profilokat, kapcsolatokat és csoportokat; az üzenetek küldése 2 rétegű végpontok közötti titkosítással történik.", + "hero-overlay-card-1-p-6": "További leírást a SimpleX ismertetőben olvashat.", + "hero-overlay-card-2-p-1": "Ha a felhasználók állandó azonosítóval rendelkeznek, még akkor is, ha ez csak egy véletlenszerű szám, például egy munkamenet-azonosító, fennáll annak a veszélye, hogy a szolgáltató vagy egy támadó megfigyelheti, hogyan kapcsolódnak a felhasználók, és hány üzenetet küldenek.", + "hero-overlay-card-2-p-2": "Ezt az információt aztán összefüggésbe hozhatják a meglévő nyilvános közösségi hálózatokkal, és meghatározhatnak néhány valódi személyazonosságot.", + "hero-overlay-card-2-p-3": "Még a Tor v3 szolgáltatásokat használó, legprivátabb alkalmazások esetében is, ha két különböző kapcsolattartóval beszél ugyanazon a profilon keresztül, bizonyítani tudják, hogy ugyanahhoz a személyhez kapcsolódnak.", + "hero-overlay-card-2-p-4": "A SimpleX úgy védekezik ezek ellen a támadások ellen, hogy nem tartalmaz felhasználói azonosítókat. Ha pedig használja az inkognitó módot, akkor minden egyes létrejött kapcsolatban más-más felhasználó név jelenik meg, így elkerülhető a közöttük lévő összefüggések bizonyítása.", + "hero-overlay-card-3-p-1": "Trail of Bits egy vezető biztonsági és technológiai tanácsadó cég, amelynek ügyfelei közé tartoznak a nagy technológiai cégek, kormányzati ügynökségek és jelentős blokklánc projektek.", + "hero-overlay-card-3-p-2": "A Trail of Bits 2022 novemberében áttekintette a SimpleX platform kriptográfiai és hálózati komponenseit.", + "simplex-network-overlay-card-1-li-1": "A P2P-hálózatok az üzenetek továbbítására a DHT valamelyik változatát használják. A DHT kialakításakor egyensúlyt kell teremteni a kézbesítési garancia és a késleltetés között. A SimpleX jobb kézbesítési garanciával és alacsonyabb késleltetéssel rendelkezik, mint a P2P, mivel az üzenet redundánsan, a címzett által kiválasztott kiszolgálók segítségével több kiszolgálón keresztül párhuzamosan továbbítható. A P2P-hálózatokban az üzenet O(log N) csomóponton halad át szekvenciálisan, az algoritmus által kiválasztott csomópontok segítségével.", + "simplex-network-overlay-card-1-li-2": "A SimpleX kialakítása a legtöbb P2P-hálózattól eltérően nem rendelkezik semmiféle globális felhasználói azonosítóval, még ideiglenesen sem, és csak ideiglenes páros azonosítókat használ, ami jobb névtelenséget és metaadatvédelmet biztosít.", + "simplex-network-overlay-card-1-li-3": "A P2P nem oldja meg a MITM-támadás problémát, és a legtöbb létező implementáció nem használ sávon kívüli üzeneteket a kezdeti kulcscseréhez. A SimpleX a kezdeti kulcscseréhez sávon kívüli üzeneteket, vagy bizonyos esetekben már meglévő biztonságos és megbízható kapcsolatokat használ.", + "simplex-network-overlay-card-1-li-5": "Minden ismert P2P-hálózat sebezhető Sybil támadással, mert minden egyes csomópont felderíthető, és a hálózat egészként működik. A támadások enyhítésére szolgáló ismert intézkedés lehet egy központi kiszolgáló (pl.: tracker), vagy egy drága tanúsítvány. A SimpleX hálózat nem ismeri fel a kiszolgálókat, töredezett és több elszigetelt alhálózatként működik, ami lehetetlenné teszi az egész hálózatra kiterjedő támadásokat.", + "simplex-network-overlay-card-1-li-6": "A P2P-hálózatok sebezhetőek lehetnek a DRDoS-támadással szemben, amikor a kliensek képesek a forgalmat újraközvetíteni és felerősíteni, ami az egész hálózatra kiterjedő szolgáltatásmegtagadást eredményez. A SimpleX kliensek csak az ismert kapcsolatból származó forgalmat továbbítják, és a támadó nem használhatja őket arra, hogy az egész hálózatban felerősítse a forgalmat.", + "privacy-matters-overlay-card-1-p-1": "Sok nagyvállalat arra használja fel az önnel kapcsolatban álló személyek adatait, hogy megbecsülje az ön jövedelmét, hogy olyan termékeket adjon el önnek, amelyekre valójában nincs is szüksége, és hogy meghatározza az árakat.", + "privacy-matters-overlay-card-1-p-2": "Az online kiskereskedők tudják, hogy az alacsonyabb jövedelműek nagyobb valószínűséggel vásárolnak azonnal, ezért magasabb árakat számíthatnak fel, vagy eltörölhetik a kedvezményeket.", + "privacy-matters-overlay-card-1-p-3": "Egyes pénzügyi és biztosítótársaságok szociális grafikonokat használnak a kamatlábak és a díjak meghatározásához. Ez gyakran arra készteti az alacsonyabb jövedelmű embereket, hogy többet fizessenek — ez az úgynevezett „szegénységi prémium”.", + "privacy-matters-overlay-card-1-p-4": "A SimpleX platform minden alternatívánál jobban védi a kapcsolatainak adatait, teljes mértékben megakadályozva, hogy a ismeretségi-hálója bármilyen vállalat vagy szervezet számára elérhetővé váljon. Még ha az emberek a SimpleX Chat által biztosított kiszolgálókat is használják, sem a felhasználók számát, sem a kapcsolataikat nem ismerjük.", + "privacy-matters-overlay-card-2-p-1": "Nem is olyan régen megfigyelhettük, hogy a nagy választásokat manipulálta egy neves tanácsadó cég, amely az ismeretségi-háló segítségével eltorzította a valós világról alkotott képünket, és manipulálta a szavazatainkat.", + "privacy-matters-overlay-card-2-p-2": "Ahhoz, hogy objektív legyen és független döntéseket tudjon hozni, az információs terét is kézben kell tartania. Ez csak akkor lehetséges, ha privát kommunikációs platformot használ, amely nem fér hozzá az ismeretségi-hálójához.", + "privacy-matters-overlay-card-2-p-3": "A SimpleX az első olyan platform, amely eleve nem rendelkezik felhasználói azonosítókkal, így jobban védi az ismeretségi-hálóját, mint bármely ismert alternatíva.", + "privacy-matters-overlay-card-3-p-1": "Mindenkinek törődnie kell a magánélet és a kommunikáció biztonságával — az ártalmatlan beszélgetések veszélybe sodorhatják, még akkor is, ha nincs semmi rejtegetnivalója.", + "privacy-matters-overlay-card-3-p-2": "Az egyik legmegdöbbentőbb a Mohamedou Ould Salahi memoárjában leírt és az „A mauritániai” c. filmben bemutatott történet. Őt bírósági tárgyalás nélkül a guantánamói táborba zárták, és ott kínozták 15 éven át, miután egy afganisztáni rokonát telefonon felhívta, akit azzal gyanúsítottak a hatóságok, hogy köze van a 9/11-es merényletekhez, holott Salahi az előző 10 évben Németországban élt.", + "privacy-matters-overlay-card-3-p-3": "Átlagos embereket letartóztatnak azért, amit online megosztanak, még „névtelen” fiókjaikon keresztül is, még demokratikus országokban is.", + "privacy-matters-overlay-card-3-p-4": "Nem elég, ha csak egy végpontok között titkosított üzenetküldőt használunk, mindannyiunknak olyan üzenetküldőket kell használnunk, amelyek védik személyes ismerőseink magánéletét — akikkel kapcsolatban állunk.", + "simplex-unique-overlay-card-1-p-1": "Más üzenetküldő platformoktól eltérően a SimpleX nem rendel azonosítókat a felhasználókhoz. Nem támaszkodik telefonszámokra, tartomány-alapú címekre (mint az e-mail, XMPP vagy a Matrix), felhasználónevekre, nyilvános kulcsokra vagy akár véletlenszerű számokra a felhasználók azonosításához — nem tudjuk, hogy hányan használják a SimpleX-kiszolgálóinkat.", + "simplex-unique-overlay-card-1-p-2": "Az üzenetek kézbesítéséhez a SimpleX az egyirányú üzenet várakoztatást használ páronkénti névtelen címekkel, külön a fogadott és külön az elküldött üzenetek számára, általában különböző kiszolgálókon keresztül. A SimpleX használata olyan, mintha minden egyes kapcsolatnak más-más “eldobható” e-mail címe vagy telefonja lenne és nem kell ezeket gondosan kezelni.", + "simplex-unique-overlay-card-1-p-3": "Ez a kialakítás megvédi annak titkosságát, hogy kivel kommunikál, elrejtve azt a SimpleX platform kiszolgálói és a megfigyelők elől. IP-címének a kiszolgálók elől való elrejtéséhez azt teheti meg, hogy Tor-on keresztül kapcsolódik a SimpleX kiszolgálókhoz.", + "simplex-unique-overlay-card-2-p-1": "Mivel ön nem rendelkezik azonosítóval a SimpleX platformon, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", + "simplex-unique-overlay-card-2-p-2": "Még az opcionális felhasználói cím esetében is, bár spam kapcsolatfelvételi kérések küldésére használható, megváltoztathatja vagy teljesen törölheti azt anélkül, hogy elveszítené a meglévő kapcsolatait.", + "simplex-unique-overlay-card-3-p-1": "A SimpleX Chat az összes felhasználói adatot kizárólag a klienseken tárolja egy hordozható titkosított adatbázis-formátumban, amely exportálható és átvihető bármely más támogatott eszközre.", + "simplex-unique-overlay-card-3-p-2": "A végpontok között titkosított üzenetek átmenetileg a SimpleX átjátszó-kiszolgálókon tartózkodnak, amíg be nem érkeznek a címzetthez, majd véglegesen törlődnek onnan.", + "simplex-unique-overlay-card-3-p-3": "A föderált hálózatok kiszolgálóitól (e-mail, XMPP vagy Matrix) eltérően a SimpleX kiszolgálók nem tárolják a felhasználói fiókokat, csak továbbítják az üzeneteket, így védve mindkét fél magánéletét.", + "simplex-unique-overlay-card-3-p-4": "A küldött és a fogadott kiszolgálóforgalom között nincsenek közös azonosítók vagy titkosított szövegek — ha bárki megfigyeli, nem tudja könnyen megállapítani, hogy ki kivel kommunikál, még akkor sem, ha a TLS-t kompromittálják.", + "simplex-unique-overlay-card-4-p-1": "Használhatja a SimpleX-et saját kiszolgálóival, és továbbra is kommunikálhat azokkal, akik az általunk biztosított, előre konfigurált kiszolgálókat használják.", + "simplex-unique-overlay-card-4-p-2": "A SimpleX platform nyitott protokollt használ és SDK-t biztosít a chatbotok létrehozásához, lehetővé téve olyan szolgáltatások megvalósítását, amelyekkel a felhasználók a SimpleX Chat alkalmazásokon keresztül léphetnek kapcsolatba — mi már nagyon várjuk, hogy milyen SimpleX szolgáltatásokat készítenek a lelkes közreműködők.", + "simplex-unique-overlay-card-4-p-3": "Ha a SimpleX platformra való fejlesztést fontolgatja, például a SimpleX-alkalmazások felhasználóinak szánt chatbotot, vagy a SimpleX Chat Jegyzék bot integrálását más mobilalkalmazásba, lépjen velünk kapcsolatba, ha bármilyen tanácsot vagy támogatást szeretne kapni.", + "simplex-unique-card-1-p-1": "A SimpleX védi az ön profiljához tartozó kapcsolatait és metaadatait, elrejtve azokat a SimpleX platform kiszolgálói és a megfigyelők elől.", + "simplex-unique-card-1-p-2": "Minden más létező üzenetküldő platformtól eltérően a SimpleX nem rendelkezik a felhasználókhoz rendelt azonosítókkal — még véletlenszerű számokkal sem.", + "simplex-unique-card-2-p-1": "Mivel a SimpleX platformon nincs azonosítója vagy állandó címe, senki sem tud kapcsolatba lépni önnel, hacsak nem oszt meg egy egyszeri vagy ideiglenes felhasználói címet, például QR-kódot vagy hivatkozást.", + "simplex-unique-card-3-p-1": "A SimpleX Chat az összes felhasználói adatot kizárólag az klienseken tárolja egy hordozható titkosított adatbázis-formátumban —, amely exportálható és átvihető bármely más támogatott eszközre.", + "simplex-unique-card-3-p-2": "A végpontok között titkosított üzenetek átmenetileg a SimpleX átjátszó-kiszolgálókon tartózkodnak, amíg be nem érkeznek a címzetthez, majd végleges törlésre kerülnek.", + "simplex-unique-card-4-p-1": "A SimpleX hálózat teljesen decentralizált és független bármely kriptopénztől vagy bármely más platformtól, kivéve az internetet.", + "simplex-unique-card-4-p-2": "Használhatja a SimpleX-et saját kiszolgálóival vagy az általunk biztosított kiszolgálókkal, és továbbra is kapcsolódhat bármely felhasználóhoz.", + "join": "Csatlakozás", + "we-invite-you-to-join-the-conversation": "Meghívjuk önt, hogy csatlakozzon a beszélgetéshez", + "join-the-REDDIT-community": "Csatlakozzon a REDDIT közösséghez", + "join-us-on-GitHub": "Csatlakozzon hozzánk a GitHubon", + "donate-here-to-help-us": "Adományozzon itt, hogy segítsen nekünk", + "sign-up-to-receive-our-updates": "Regisztráljon az oldalra, hogy megkapja frissítéseinket", + "enter-your-email-address": "Adja meg az e-mail címét", + "get-simplex": "SimpleX Desktop alkalmazás letöltése", + "why-simplex-is": "A SimpleX mitől", + "unique": "egyedülálló", + "learn-more": "Tudjon meg többet", + "more-info": "További információ", + "hide-info": "Információ elrejtése", + "contact-hero-subheader": "Szkennelje be a QR-kódot a SimpleX Chat alkalmazással telefonján vagy táblagépén.", + "contact-hero-p-1": "A hivatkozásban szereplő nyilvános kulcsokat és az üzenetek várakoztatási címét NEM küldi el a hálózaton keresztül az oldal megtekintésekor — ezeket a hivatkozás URL-jének hash-töredéke tartalmazza.", + "contact-hero-p-2": "Még nem töltötte le a SimpleX Chatet?", + "contact-hero-p-3": "Az alkalmazás letöltéséhez használja az alábbi linkeket.", + "scan-qr-code-from-mobile-app": "QR-kód beolvasása mobilalkalmazásból", + "to-make-a-connection": "A kapcsolat létrehozásához:", + "install-simplex-app": "Telepítse a SimpleX alkalmazást", + "open-simplex-app": "Simplex alkalmazás megnyitása", + "tap-the-connect-button-in-the-app": "Koppintson a „kapcsolódás” gombra az alkalmazásban", + "scan-the-qr-code-with-the-simplex-chat-app": "A QR-kód beolvasása a SimpleX Chat alkalmazással", + "scan-the-qr-code-with-the-simplex-chat-app-description": "A hivatkozásban szereplő nyilvános kulcsokat és az üzenetek várakoztatási címét NEM küldjük el a hálózaton keresztül, amikor ezt az oldalt megtekinti —
ezek a hivatkozás URL-jének hash-töredékében szerepelnek.", + "installing-simplex-chat-to-terminal": "A SimpleX chat telepítése terminálba", + "use-this-command": "Használja ezt a parancsot:", + "see-simplex-chat": "Lásd SimpleX Chat", + "connect-in-app": "Kapcsolódás az alkalmazásban", + "the-instructions--source-code": "az utasításokat, hogyan töltse le vagy fordítsa le a forráskódból.", + "if-you-already-installed-simplex-chat-for-the-terminal": "Ha már telepítette a SimpleX Chat-et a terminálba", + "if-you-already-installed": "Ha már telepítette a", + "simplex-chat-for-the-terminal": "SimpleX Chat-et a terminálba", + "copy-the-command-below-text": "másolja be az alábbi parancsot, és használja a csevegésben:", + "privacy-matters-section-header": "Miért számít az adatvédelem", + "privacy-matters-section-subheader": "A metaadatok védelmének megőrzése — kivel beszélget — megvédi a következőktől:", + "privacy-matters-section-label": "Győződjön meg róla, hogy az üzenetküldő amit használ nem fér hozzá az adataidhoz!", + "simplex-private-section-header": "Mitől lesz a SimpleX privát", + "simplex-network-section-header": "SimpleX hálózat", + "simplex-network-section-desc": "A Simplex Chat a P2P és a föderált hálózatok előnyeinek kombinálásával biztosítja a legjobb adatvédelmet.", + "simplex-network-1-desc": "Minden üzenet a kiszolgálókon keresztül kerül elküldésre, ami jobb metaadat-védelmet és megbízható aszinkron üzenetkézbesítést biztosít, miközben elkerülhető a sok", + "simplex-network-2-header": "A föderált hálózatokkal ellentétben", + "simplex-network-2-desc": "A SimpleX átjátszó kiszolgálók NEM tárolnak felhasználói profilokat, kapcsolatokat és kézbesített üzeneteket, NEM csatlakoznak egymáshoz, és NINCS kiszolgáló könyvtár.", + "simplex-network-3-header": "SimpleX hálózat", + "simplex-network-3-desc": "a kiszolgálók egyirányú üzenet várakoztatásokat biztosítanak a felhasználók összekapcsolásához, de nem látják a hálózati kapcsolati gráfot; azt csak a felhasználók látják.", + "comparison-section-header": "Összehasonlítás más protokollokkal", + "protocol-1-text": "Signal, nagy platformok", + "protocol-2-text": "XMPP, Matrix", + "protocol-3-text": "P2P protokollok", + "comparison-point-1-text": "Globális személyazonosságot igényel", + "comparison-point-2-text": "MITM lehetősége", + "comparison-point-4-text": "Egyetlen vagy központosított hálózat", + "comparison-point-5-text": "Központi komponens vagy más hálózati szintű támadás", + "no": "Nem", + "no-private": "Nem - privát", + "no-secure": "Nem - biztonságos", + "no-resilient": "Nem - ellenálló", + "no-decentralized": "Nem - decentralizált", + "no-federated": "Nem - föderált", + "comparison-section-list-point-1": "Általában telefonszám alapján, néhány esetben felhasználónév alapján", + "comparison-section-list-point-2": "DNS-alapú címek", + "comparison-section-list-point-3": "Nyilvános kulcs vagy más globális egyedi azonosító", + "comparison-section-list-point-4a": "A SimpleX átjátszók nem veszélyeztethetik az e2e titkosítást. Biztonsági kód ellenőrzése a sávon kívüli csatorna elleni támadás mérséklésére", + "comparison-section-list-point-4": "Ha az üzemeltető kiszolgálói veszélybe kerülnek. Ellenőrizze a biztonsági kódot a Signal-ban és néhány más alkalmazásban, hogy csökkentse a veszélyt", + "comparison-section-list-point-5": "Nem védi a felhasználók metaadatait", + "comparison-section-list-point-6": "Bár a P2P elosztott, de nem föderált - egyetlen hálózatként működnek", + "comparison-section-list-point-7": "A P2P-hálózatoknak vagy van egy központi hitelesítője, vagy az egész hálózat kompromittálódhat", + "see-here": "lásd itt", + "guide-dropdown-1": "Gyors indítás", + "guide-dropdown-2": "Üzenetek küldése", + "guide-dropdown-3": "Titkos csoportok", + "guide-dropdown-4": "Csevegő profilok", + "guide-dropdown-5": "Adatkezelés", + "guide-dropdown-6": "Hang- és videó hívások", + "guide-dropdown-7": "Adatvédelem és biztonság", + "guide-dropdown-8": "Alkalmazás beállításai", + "guide": "Útmutató", + "docs-dropdown-1": "SimpleX platform", + "docs-dropdown-2": "Android fájlok elérése", + "docs-dropdown-3": "Hozzáférés a csevegő adatbázishoz", + "docs-dropdown-8": "SimpleX jegyzék szolgáltatás", + "docs-dropdown-9": "Letöltések", + "f-droid-page-simplex-chat-repo-section-text": "Ha hozzá szeretné adni az F-Droid klienséhez, olvassa be a QR-kódot, vagy használja ezt az URL-t:", + "signing-key-fingerprint": "Aláíró kulcs ujjlenyomata (SHA-256)", + "f-droid-org-repo": "F-Droid.org tároló", + "stable-versions-built-by-f-droid-org": "F-Droid.org által készített stabil verziók", + "releases-to-this-repo-are-done-1-2-days-later": "A kiadások ebben a tárolóban néhány napot késnek", + "f-droid-page-f-droid-org-repo-section-text": "A SimpleX Chat és az F-Droid.org tárolók különböző kulcsokkal írják alá az összeállításokat. A váltáshoz exportálja a csevegési adatbázist és telepítse újra az alkalmazást.", + "jobs": "Csatlakozzon a csapathoz", + "please-enable-javascript": "Engedélyezze a JavaScriptet a QR-kód megjelenítéséhez.", + "please-use-link-in-mobile-app": "Használja a mobilalkalmazásban található hivatkozást", + "contact-hero-header": "Kapott egy címet a SimpleX Chat-en való kapcsolódáshoz", + "invitation-hero-header": "Kapott egy egyszer használatos hivatkozást a SimpleX Chat-en való kapcsolódáshoz", + "simplex-network-overlay-card-1-li-4": "A P2P-megvalósításokat egyes internetszolgáltatók blokkolhatják (mint például a BitTorrent). A SimpleX átvitel-független - a szabványos webes protokollokon, pl. WebSockets-en keresztül is működik.", + "simplex-private-card-4-point-2": "A SimpleX Tor-on keresztüli használatához telepítse az Orbot alkalmazást és engedélyezze a SOCKS5 proxy-t (vagy a VPN-t az iOS-ban).", + "simplex-private-card-5-point-1": "A SimpleX minden titkosítási réteghez tartalomkitöltést használ, hogy meghiúsítsa az üzenetméret ellen irányuló támadásokat.", + "simplex-private-card-5-point-2": "A kiszolgálók és a hálózatot megfigyelők számára a különböző méretű üzenetek egyformának tűnnek.", + "privacy-matters-1-title": "Hirdetés és árdiszkrimináció", + "hero-overlay-card-1-p-3": "Ön határozza meg, hogy melyik kiszolgáló(ka)t használja az üzenetek fogadására, a kapcsolatokhoz — azokat a kiszolgálókat, amelyeket az üzenetek küldésére használ. Minden beszélgetés két különböző kiszolgálót használ.", + "hero-overlay-card-3-p-3": "További információk a közleményben.", + "simplex-network-overlay-card-1-p-1": "A P2P üzenetküldő protokollok és alkalmazások számos problémával küzdenek, amelyek miatt kevésbé megbízhatóak, mint a SimpleX, bonyolultabb az elemzésük és többféle támadással szemben sebezhetőek.", + "chat-bot-example": "Chat bot példa", + "simplex-private-3-title": "Biztonságos, hitelesített
TLS adatátvitel", + "github-repository": "GitHub tároló", + "tap-to-close": "Koppintson a bezáráshoz", + "simplex-network-1-header": "A P2P hálózatokkal ellentétben", + "simplex-network-1-overlay-linktext": "a P2P hálózat problémái", + "comparison-point-3-text": "Függés a DNS-től", + "yes": "Igen", + "guide-dropdown-9": "Kapcsolatok létrehozása", + "docs-dropdown-4": "SMP-kiszolgáló üzemeltetése", + "docs-dropdown-5": "XFTP-kiszolgáló üzemeltetése", + "docs-dropdown-6": "WebRTC kiszolgálók", + "docs-dropdown-7": "SimpleX Chat honosítása", + "docs-dropdown-10": "Átláthatóság", + "docs-dropdown-11": "GY.I.K.", + "docs-dropdown-12": "Biztonság", + "newer-version-of-eng-msg": "Ennek az oldalnak van egy újabb angol nyelvű változata.", + "click-to-see": "Kattintson a megtekintéséhez", + "menu": "Menü", + "on-this-page": "Ezen az oldalon", + "back-to-top": "Vissza a tetejére", + "glossary": "Fogalomtár", + "simplex-chat-via-f-droid": "SimpleX Chat az F-Droidon keresztül", + "simplex-chat-repo": "SimpleX Chat tároló", + "stable-and-beta-versions-built-by-developers": "A fejlesztők által készített stabil és béta verziók" +} \ No newline at end of file diff --git a/website/langs/it.json b/website/langs/it.json index c2425f93fd..64d3b4b8a4 100644 --- a/website/langs/it.json +++ b/website/langs/it.json @@ -233,7 +233,7 @@ "guide-dropdown-8": "Impostazioni dell'app", "docs-dropdown-7": "Traduci SimpleX Chat", "glossary": "Glossario", - "releases-to-this-repo-are-done-1-2-days-later": "Le pubblicazioni su questo repo avvengono 1-2 giorni dopo", + "releases-to-this-repo-are-done-1-2-days-later": "Le pubblicazioni su questo repo avvengono diversi giorni dopo", "f-droid-page-f-droid-org-repo-section-text": "I repository di SimpleX Chat e F-Droid.org firmano i pacchetti con chiavi diverse. Per passare da uno all'altro, esporta il database della chat e reinstalla l'app.", "signing-key-fingerprint": "Impronta della chiave di firma (SHA-256)", "f-droid-org-repo": "Repo di F-Droid.org", diff --git a/website/langs/ja.json b/website/langs/ja.json index 3ac040272f..4b30104615 100644 --- a/website/langs/ja.json +++ b/website/langs/ja.json @@ -123,7 +123,7 @@ "no-resilient": "いいえ - 弾力性", "hide-info": "情報を隠す", "privacy-matters-overlay-card-3-p-4": "エンドツーエンドで暗号化されたメッセンジャーを使用するだけでは十分ではありません。私たちは皆、個人ネットワークのプライバシーを保護するメッセンジャーを使用する必要があります — 私たちがつながっているのは誰なのか。", - "releases-to-this-repo-are-done-1-2-days-later": "このリポジトリへのリリースは 1 ~ 2 日後に行われます", + "releases-to-this-repo-are-done-1-2-days-later": "このリポジトリへのリリースは数日後に行われます", "comparison-point-1-text": "グローバル ID が必要", "comparison-section-list-point-5": "ユーザーのメタデータのプライバシーを保護しない", "hero-overlay-card-2-p-2": "その後、この情報を既存の公開ソーシャル ネットワークと関連付けて、本当の身元を特定することができます。", diff --git a/website/langs/nl.json b/website/langs/nl.json index 15729fbfe9..4f6724e3ff 100644 --- a/website/langs/nl.json +++ b/website/langs/nl.json @@ -240,7 +240,7 @@ "f-droid-org-repo": "F-Droid.org repo", "signing-key-fingerprint": "Signing key fingerprint (SHA-256)", "stable-versions-built-by-f-droid-org": "Stabiele versies gebouwd door F-Droid.org", - "releases-to-this-repo-are-done-1-2-days-later": "De releases voor deze repository vinden 1-2 dagen later plaats", + "releases-to-this-repo-are-done-1-2-days-later": "De releases voor deze repository vinden enkele dagen later plaats", "f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat- en F-Droid.org-repository's ondertekenen builds met de verschillende sleutels. Om over te stappen, alstublieft exporteer de chatdatabase en installeer de app opnieuw.", "docs-dropdown-8": "SimpleX Directory Service", "comparison-section-list-point-4a": "SimpleX relais kunnen de e2e-versleuteling niet in gevaar brengen. Controleer de beveiligingscode om aanvallen op out-of-band kanalen te beperken", diff --git a/website/langs/pl.json b/website/langs/pl.json index bbfb2dd9b7..510915831d 100644 --- a/website/langs/pl.json +++ b/website/langs/pl.json @@ -242,7 +242,7 @@ "signing-key-fingerprint": "Odcisk klucza podpisu (SHA-256)", "f-droid-org-repo": "Repo F-Droid.org", "stable-versions-built-by-f-droid-org": "Wersje stabilne zbudowane przez F-Droid.org", - "releases-to-this-repo-are-done-1-2-days-later": "Wydania na tym repo są 1-2 dni później", + "releases-to-this-repo-are-done-1-2-days-later": "Wydania na tym repo są kilka dni później", "comparison-section-list-point-4a": "Przekaźniki SimpleX nie mogą skompromitować szyfrowania e2e. Zweryfikuj kody bezpieczeństwa aby złagodzić atak na kanał pozapasmowy", "hero-overlay-3-title": "Ocena bezpieczeństwa", "hero-overlay-card-3-p-2": "Trail of Bits przejrzał komponenty kryptograficzne i sieciowe platformy SimpleX w listopadzie 2022.", diff --git a/website/langs/uk.json b/website/langs/uk.json index 6289b44ca8..f37feb55ef 100644 --- a/website/langs/uk.json +++ b/website/langs/uk.json @@ -1,7 +1,7 @@ { "features": "Особливості", "simplex-explained-tab-3-text": "3. Що бачать сервери", - "terms-and-privacy-policy": "Умови та політика конфіденційності", + "terms-and-privacy-policy": "Політика конфіденційності", "feature-4-title": "Голосові повідомлення з шифруванням від кінця до кінця", "feature-5-title": "Зникнення повідомлень", "simplex-private-card-3-point-3": "Відновлення з'єднання вимкнено для запобігання атакам на сесію.", @@ -240,7 +240,7 @@ "stable-versions-built-by-f-droid-org": "Стабільні версії, побудовані F-Droid.org", "simplex-chat-repo": "Репозитарій SimpleX Chat", "f-droid-org-repo": "Репозитарій F-Droid.org", - "releases-to-this-repo-are-done-1-2-days-later": "Релізи в цей репозитарій робляться за 1-2 дні пізніше", + "releases-to-this-repo-are-done-1-2-days-later": "Релізи в це репо відбуваються на кілька днів пізніше", "stable-and-beta-versions-built-by-developers": "Стабільні та бета-версії, побудовані розробниками", "f-droid-page-f-droid-org-repo-section-text": "SimpleX Chat та репозитарії F-Droid.org підписують збірки різними ключами. Щоб переключитися, будь ласка, експортуйте базу даних чату та перевстановіть додаток.", "hero-overlay-3-title": "Оцінка безпеки", @@ -252,5 +252,8 @@ "comparison-section-list-point-4a": "Ретранслятори SimpleX не можуть порушити e2e-шифрування. Перевірте безпековий код для зменшення ризику атаки на зовнішньобандовий канал", "docs-dropdown-9": "Завантаження", "please-enable-javascript": "Будь ласка, увімкніть JavaScript, щоб побачити QR-код.", - "please-use-link-in-mobile-app": "Будь ласка, скористайтеся посиланням у мобільному додатку" + "please-use-link-in-mobile-app": "Будь ласка, скористайтеся посиланням у мобільному додатку", + "docs-dropdown-11": "ПОШИРЕНІ ЗАПИТАННЯ", + "docs-dropdown-10": "Прозорість", + "docs-dropdown-12": "Безпека" } diff --git a/website/langs/zh_Hans.json b/website/langs/zh_Hans.json index a81f4d123c..01f6149669 100644 --- a/website/langs/zh_Hans.json +++ b/website/langs/zh_Hans.json @@ -235,7 +235,7 @@ "glossary": "术语表", "signing-key-fingerprint": "签名密钥指纹 (SHA-256)", "simplex-chat-via-f-droid": "通过 F-Droid 下载 SimpleX", - "releases-to-this-repo-are-done-1-2-days-later": "此存储库的版本将延迟 1-2 天发布", + "releases-to-this-repo-are-done-1-2-days-later": "此存储库的版本将延迟数天发布", "f-droid-org-repo": "F-Droid.org 存储库", "stable-versions-built-by-f-droid-org": "由 F-Droid.org 构建的稳定版本", "simplex-chat-repo": "SimpleX 存储库", diff --git a/website/src/_includes/blog_previews/20240601.html b/website/src/_includes/blog_previews/20240601.html new file mode 100644 index 0000000000..5e0ca2de49 --- /dev/null +++ b/website/src/_includes/blog_previews/20240601.html @@ -0,0 +1,2 @@ +

As lawmakers grapple with the serious issue of child exploitation online, + some proposed solutions would fuel the very problem they aim to solve.

\ No newline at end of file