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/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/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff b/apps/ios/SimpleX Localizations/bg.xcloc/Localized Contents/bg.xliff
index 3d0bb2bf2c..8657b17c03 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Мобилна мрежа
@@ -1948,6 +1960,10 @@ This cannot be undone!
Настолни устройстваNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ DevelopРазработване
@@ -2053,11 +2069,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 +2645,7 @@ This cannot be undone!
Error: %@Грешка: %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2713,6 +2737,10 @@ This cannot be undone!
Файл: %@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaФайлове и медия
@@ -2818,6 +2846,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,6 +3671,10 @@ This is your link for group %@!
Потвърждениe за доставка на съобщения!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftЧернова на съобщение
@@ -3653,6 +3695,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 +3833,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 +3863,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 +4408,19 @@ Error: %@
Поверителни имена на файлове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,6 +4501,10 @@ Error: %@
Забрани изпращането на гласови съобщения.No comment provided by engineer.
+
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screenЗащити екрана на приложението
@@ -4554,11 +4615,6 @@ Error: %@
Получаващият адрес ще бъде променен към друг сървър. Промяната на адреса ще завърши, след като подателят е онлайн.No comment provided by engineer.
-
- Receiving concurrency
- Паралелност на получаване
- No comment provided by engineer.
- Receiving file will be stopped.Получаващият се файл ще бъде спрян.
@@ -5019,6 +5075,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 +5188,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 +5207,10 @@ Error: %@
Тестът на сървъра е неуспешен!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ ServersСървъри
@@ -5259,11 +5331,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 +5666,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 +5855,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 +5951,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 +5996,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 +6147,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 +6390,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!Грешна парола!
@@ -7455,11 +7562,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 +7640,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..8d52e045b1 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 relaySpojení přes relé
@@ -1047,6 +1055,10 @@
Nelze přijmout souborNo comment provided by engineer.
+
+ Capacity exceeded - recipient did not receive previously sent messages.
+ snd error text
+ CellularNo comment provided by engineer.
@@ -1870,6 +1882,10 @@ This cannot be undone!
Desktop devicesNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ DevelopVyvinout
@@ -1973,11 +1989,19 @@ This cannot be undone!
Discover via local networkNo 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 laterUdělat později
@@ -2519,7 +2543,7 @@ This cannot be undone!
Error: %@Chyba: %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2608,6 +2632,10 @@ This cannot be undone!
Soubor: %@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaSoubory a média
@@ -2706,6 +2734,16 @@ This cannot be undone!
Forwarded fromNo comment provided by engineer.
+
+ Forwarding server: %1$@
+Destination server error: %2$@
+ snd error text
+
+
+ Forwarding server: %1$@
+Error: %2$@
+ snd error text
+ Found desktopNo comment provided by engineer.
@@ -3490,6 +3528,10 @@ This is your link for group %@!
Potvrzení o doručení zprávy!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftNávrh zprávy
@@ -3510,6 +3552,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 +3677,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 profilesVíce chatovacích profilů
@@ -3661,6 +3706,10 @@ This is your link for group %@!
Network connectionNo comment provided by engineer.
+
+ Network issues - message expired after many attempts to send it.
+ snd error text
+ Network managementNo comment provided by engineer.
@@ -4181,10 +4230,18 @@ Error: %@
Soukromé názvy souborůNo comment provided by engineer.
+
+ Private message routing
+ No comment provided by engineer.
+ Private notesname of notes to self
+
+ Private routing
+ No comment provided by engineer.
+ Profile and server connectionsProfil a připojení k serveru
@@ -4261,6 +4318,10 @@ 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 screenOchrana obrazovky aplikace
@@ -4368,10 +4429,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.
@@ -4817,6 +4874,14 @@ Error: %@
Odeslat živou zprávuNo 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 notificationsOdeslat oznámení
@@ -4921,6 +4986,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 passwordServer vyžaduje autorizaci pro vytváření front, zkontrolujte heslo
@@ -4936,6 +5005,10 @@ Error: %@
Test serveru se nezdařil!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ ServersServery
@@ -5051,11 +5124,19 @@ Error: %@
Zobrazit poslední zprávyNo comment provided by engineer.
+
+ Show message status
+ No comment provided by engineer.
+ Show previewZobrazení náhleduNo comment provided by engineer.
+
+ Show → on messages sent via private routing.
+ No comment provided by engineer.
+ Show:Zobrazit:
@@ -5369,6 +5450,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 +5630,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 +5720,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 stateNeočekávaný stav přenášení
@@ -5681,6 +5765,10 @@ Před zapnutím této funkce budete vyzváni k dokončení ověření.
Neznámá chybaNo 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 +5909,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 serverPoužít server
@@ -6038,11 +6134,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áziNo 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!
@@ -7158,10 +7266,18 @@ Servery SimpleX nevidí váš profil.
neznámýconnection info
+
+ unknown relays
+ No comment provided by engineer.
+ unknown statusNo comment provided by engineer.
+
+ unprotected
+ No comment provided by engineer.
+ updated group profileaktualizoval profil skupiny
@@ -7225,6 +7341,10 @@ Servery SimpleX nevidí váš profil.
týdnůtime unit
+
+ when IP hidden
+ No comment provided by engineer.
+ yesano
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..b21e35382a 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,10 @@
Erlauben Sie verschwindende Nachrichten nur dann, wenn es Ihr Kontakt ebenfalls erlaubt.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)Erlauben Sie das unwiederbringliche Löschen von Nachrichten nur dann, wenn es Ihnen Ihr Kontakt ebenfalls erlaubt. (24 Stunden)
@@ -808,6 +812,10 @@
Sie sind bereits Mitglied der Gruppe!No comment provided by engineer.
+
+ Always use private routing.
+ No comment provided by engineer.
+ Always use relayÜber ein Relais verbinden
@@ -1088,6 +1096,10 @@
Datei kann nicht empfangen werdenNo comment provided by engineer.
+
+ Capacity exceeded - recipient did not receive previously sent messages.
+ snd error text
+ CellularZellulär
@@ -1948,6 +1960,10 @@ Das kann nicht rückgängig gemacht werden!
Desktop-GeräteNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ DevelopEntwicklung
@@ -2053,11 +2069,19 @@ Das kann nicht rückgängig gemacht werden!
Lokales Netzwerk durchsuchenNo 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.Nutzen Sie SimpleX nicht für Notrufe.No comment provided by engineer.
+
+ Do NOT use private routing.
+ No comment provided by engineer.
+ Do it laterSpäter wiederholen
@@ -2621,7 +2645,7 @@ Das kann nicht rückgängig gemacht werden!
Error: %@Fehler: %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2713,6 +2737,10 @@ Das kann nicht rückgängig gemacht werden!
Datei: %@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaDateien & Medien
@@ -2818,6 +2846,16 @@ Das kann nicht rückgängig gemacht werden!
Weitergeleitet ausNo comment provided by engineer.
+
+ Forwarding server: %1$@
+Destination server error: %2$@
+ snd error text
+
+
+ Forwarding server: %1$@
+Error: %2$@
+ snd error text
+ Found desktopGefundener Desktop
@@ -3633,6 +3671,10 @@ Das ist Ihr Link für die Gruppe %@!
Empfangsbestätigungen für Nachrichten!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftNachrichtenentwurf
@@ -3653,6 +3695,14 @@ 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
+ No comment provided by engineer.
+
+
+ Message routing mode
+ No comment provided by engineer.
+ Message source remains private.Die Nachrichtenquelle bleibt privat.
@@ -3783,11 +3833,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 profilesMehrere Chat-Profile
@@ -3818,6 +3863,10 @@ Das ist Ihr Link für die Gruppe %@!
NetzwerkverbindungNo comment provided by engineer.
+
+ Network issues - message expired after many attempts to send it.
+ snd error text
+ Network managementNetzwerk-Verwaltung
@@ -4004,12 +4053,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 +4408,19 @@ Fehler: %@
Neutrale DateinamenNo comment provided by engineer.
+
+ Private message routing
+ No comment provided by engineer.
+ Private notesPrivate Notizenname of notes to self
+
+ Private routing
+ No comment provided by engineer.
+ Profile and server connectionsProfil und Serververbindungen
@@ -4444,6 +4501,10 @@ Fehler: %@
Das Senden von Sprachnachrichten nicht erlauben.No comment provided by engineer.
+
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screenApp-Bildschirm schützen
@@ -4554,11 +4615,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.
@@ -5019,6 +5075,14 @@ Fehler: %@
Live Nachricht sendenNo 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 notificationsBenachrichtigungen senden
@@ -5124,6 +5188,10 @@ Fehler: %@
Gesendete Nachrichten werden nach der eingestellten Zeit gelöscht.No comment provided by engineer.
+
+ Server address is incompatible with network settings.
+ srv error text.
+ Server requires authorization to create queues, check passwordUm Warteschlangen zu erzeugen benötigt der Server eine Authentifizierung. Bitte überprüfen Sie das Passwort
@@ -5139,6 +5207,10 @@ Fehler: %@
Server Test ist fehlgeschlagen!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ ServersServer
@@ -5259,11 +5331,19 @@ Fehler: %@
Letzte Nachrichten anzeigenNo comment provided by engineer.
+
+ Show message status
+ No comment provided by engineer.
+ Show previewVorschau anzeigenNo comment provided by engineer.
+
+ Show → on messages sent via private routing.
+ No comment provided by engineer.
+ Show:Anzeigen:
@@ -5586,6 +5666,10 @@ Dies kann passieren, wenn es einen Fehler gegeben hat oder die Verbindung kompro
Wenn sie Nachrichten oder Kontaktanfragen empfangen, kann Sie die App benachrichtigen - Um dies zu aktivieren, öffnen Sie bitte die Einstellungen.No comment provided by engineer.
+
+ The app 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.Die Änderung des Datenbank-Passworts konnte nicht abgeschlossen werden.
@@ -5771,6 +5855,10 @@ 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.
+ 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 +5951,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 stateUnerwarteter Migrationsstatus
@@ -5913,6 +5996,10 @@ Sie werden aufgefordert, die Authentifizierung abzuschließen, bevor diese Funkt
Unbekannter FehlerNo 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.Aktivieren Sie den Modus "Bitte nicht stören", um Unterbrechungen zu vermeiden, es sei denn, Sie verwenden die iOS Anrufschnittstelle.
@@ -6060,6 +6147,14 @@ 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.
+ No comment provided by engineer.
+
+
+ Use private routing with unknown servers.
+ No comment provided by engineer.
+ Use serverServer nutzen
@@ -6295,11 +6390,23 @@ 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.
+ 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 passphraseFalsches Datenbank-PasswortNo comment provided by engineer.
+
+ Wrong key or unknown connection - most likely this connection is deleted.
+ snd error text
+ Wrong passphrase!Falsches Passwort!
@@ -7455,11 +7562,19 @@ SimpleX-Server können Ihr Profil nicht einsehen.
Unbekanntconnection info
+
+ unknown relays
+ No comment provided by engineer.
+ unknown statusunbekannter Gruppenmitglieds-StatusNo comment provided by engineer.
+
+ unprotected
+ No comment provided by engineer.
+ updated group profileAktualisiertes Gruppenprofil
@@ -7525,6 +7640,10 @@ SimpleX-Server können Ihr Profil nicht einsehen.
Wochentime unit
+
+ when IP hidden
+ No comment provided by engineer.
+ yesJa
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..396d419a09 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 relayAlways use relay
@@ -1088,6 +1098,11 @@
Cannot receive fileNo 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
+ CellularCellular
@@ -1948,6 +1963,11 @@ This cannot be undone!
Desktop devicesNo comment provided by engineer.
+
+ Destination server error: %@
+ Destination server error: %@
+ snd error text
+ DevelopDevelop
@@ -2053,11 +2073,21 @@ This cannot be undone!
Discover via local networkNo 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 laterDo it later
@@ -2621,7 +2651,7 @@ This cannot be undone!
Error: %@Error: %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2713,6 +2743,11 @@ This cannot be undone!
File: %@No comment provided by engineer.
+
+ Files
+ Files
+ No comment provided by engineer.
+ Files & mediaFiles & media
@@ -2818,6 +2853,20 @@ This cannot be undone!
Forwarded fromNo 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 desktopFound desktop
@@ -3633,6 +3682,11 @@ This is your link for group %@!
Message delivery receipts!No comment provided by engineer.
+
+ Message delivery warning
+ Message delivery warning
+ item status text
+ Message draftMessage draft
@@ -3653,6 +3707,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 +3847,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 profilesMultiple chat profiles
@@ -3818,6 +3877,11 @@ This is your link for group %@!
Network connectionNo 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 managementNetwork management
@@ -4359,11 +4423,21 @@ Error: %@
Private filenamesNo comment provided by engineer.
+
+ Private message routing
+ Private message routing
+ No comment provided by engineer.
+ Private notesPrivate notesname of notes to self
+
+ Private routing
+ Private routing
+ No comment provided by engineer.
+ Profile and server connectionsProfile and server connections
@@ -4444,6 +4518,11 @@ Error: %@
Prohibit sending voice messages.No comment provided by engineer.
+
+ Protect IP address
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screenProtect app screen
@@ -4554,11 +4633,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.
@@ -5019,6 +5093,16 @@ Error: %@
Send live messageNo 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 notificationsSend notifications
@@ -5124,6 +5208,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 passwordServer requires authorization to create queues, check password
@@ -5139,6 +5228,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
+ ServersServers
@@ -5259,11 +5353,21 @@ Error: %@
Show last messagesNo comment provided by engineer.
+
+ Show message status
+ Show message status
+ No comment provided by engineer.
+ Show previewShow previewNo 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 +5690,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 +5880,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 +5977,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 stateUnexpected migration state
@@ -5913,6 +6022,11 @@ You will be prompted to complete authentication before this feature is enabled.<
Unknown errorNo 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 +6174,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 serverUse server
@@ -6295,11 +6419,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 passphraseWrong database passphraseNo 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!
@@ -7455,11 +7594,21 @@ SimpleX servers cannot see your profile.
unknownconnection info
+
+ unknown relays
+ unknown relays
+ No comment provided by engineer.
+ unknown statusunknown statusNo comment provided by engineer.
+
+ unprotected
+ unprotected
+ No comment provided by engineer.
+ updated group profileupdated group profile
@@ -7525,6 +7674,11 @@ SimpleX servers cannot see your profile.
weekstime unit
+
+ when IP hidden
+ when IP hidden
+ No comment provided by engineer.
+ yesyes
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..da42957714 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,10 @@
Se permiten los mensajes temporales pero sólo si tu contacto también los permite para tí.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)Se permite la eliminación irreversible de mensajes pero sólo si tu contacto también la permite para tí. (24 horas)
@@ -808,6 +812,10 @@
¡Ya en proceso de unirte al grupo!No comment provided by engineer.
+
+ Always use private routing.
+ No comment provided by engineer.
+ Always use relayUsar siempre retransmisor
@@ -1040,7 +1048,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 +1096,10 @@
No se puede recibir el archivoNo comment provided by engineer.
+
+ Capacity exceeded - recipient did not receive previously sent messages.
+ snd error text
+ CellularMóvil
@@ -1948,6 +1960,10 @@ This cannot be undone!
OrdenadoresNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ DevelopDesarrollo
@@ -2053,11 +2069,19 @@ This cannot be undone!
Descubrir en red localNo 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.NO uses SimpleX para llamadas de emergencia.No comment provided by engineer.
+
+ Do NOT use private routing.
+ No comment provided by engineer.
+ Do it laterHacer más tarde
@@ -2205,7 +2229,7 @@ This cannot be undone!
Enabled for
- Activar para
+ Activado paraNo comment provided by engineer.
@@ -2621,7 +2645,7 @@ This cannot be undone!
Error: %@Error: %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2713,6 +2737,10 @@ This cannot be undone!
Archivo: %@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaArchivos y multimedia
@@ -2818,6 +2846,16 @@ This cannot be undone!
Reenviado porNo comment provided by engineer.
+
+ Forwarding server: %1$@
+Destination server error: %2$@
+ snd error text
+
+
+ Forwarding server: %1$@
+Error: %2$@
+ snd error text
+ Found desktopOrdenador encontrado
@@ -3633,6 +3671,10 @@ This is your link for group %@!
¡Confirmación de entrega de mensajes!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftBorrador de mensaje
@@ -3653,6 +3695,14 @@ 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
+ No comment provided by engineer.
+
+
+ Message routing mode
+ No comment provided by engineer.
+ Message source remains private.El autor del mensaje se mantiene privado.
@@ -3783,11 +3833,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 profilesMúltiples perfiles
@@ -3818,6 +3863,10 @@ This is your link for group %@!
Conexión de redNo comment provided by engineer.
+
+ Network issues - message expired after many attempts to send it.
+ snd error text
+ Network managementGestión de la red
@@ -4359,11 +4408,19 @@ Error: %@
Nombres de archivos privadosNo comment provided by engineer.
+
+ Private message routing
+ No comment provided by engineer.
+ Private notesNotas privadasname of notes to self
+
+ Private routing
+ No comment provided by engineer.
+ Profile and server connectionsDatos del perfil y conexiones
@@ -4376,7 +4433,7 @@ Error: %@
Profile images
- Imágenes del perfil
+ Forma de los perfilesNo comment provided by engineer.
@@ -4444,6 +4501,10 @@ Error: %@
No se permiten mensajes de voz.No comment provided by engineer.
+
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screenProteger la pantalla de la aplicación
@@ -4554,11 +4615,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.
@@ -4716,7 +4772,7 @@ Error: %@
Reset to defaults
- Restablecer valores por defecto
+ Restablecer valores predetarminadosNo comment provided by engineer.
@@ -5019,6 +5075,14 @@ Error: %@
Mensaje en vivoNo 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 notificationsEnviar notificaciones
@@ -5124,6 +5188,10 @@ 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.
+ srv error text.
+ Server requires authorization to create queues, check passwordEl servidor requiere autorización para crear colas, comprueba la contraseña
@@ -5139,6 +5207,10 @@ Error: %@
¡Error en prueba del servidor!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ ServersServidores
@@ -5259,11 +5331,19 @@ Error: %@
Mostrar último mensajeNo comment provided by engineer.
+
+ Show message status
+ No comment provided by engineer.
+ Show previewMostrar vista previaNo comment provided by engineer.
+
+ Show → on messages sent via private routing.
+ No comment provided by engineer.
+ Show:Mostrar:
@@ -5586,6 +5666,10 @@ Puede ocurrir por algún bug o cuando la conexión está comprometida.
La aplicación puede notificarte cuando recibas mensajes o solicitudes de contacto: por favor, abre la configuración para activarlo.No comment provided by engineer.
+
+ The app 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.El intento de cambiar la contraseña de la base de datos no se ha completado.
@@ -5771,6 +5855,10 @@ 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.
+ 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 +5951,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 stateEstado de migración inesperado
@@ -5913,6 +5996,10 @@ Se te pedirá que completes la autenticación antes de activar esta función.Error desconocido
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.A menos que utilices la interfaz de llamadas de iOS, activa el modo No molestar para evitar interrupciones.
@@ -6061,6 +6148,14 @@ 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.
+ No comment provided by engineer.
+
+
+ Use private routing with unknown servers.
+ No comment provided by engineer.
+ Use serverUsar servidor
@@ -6296,11 +6391,23 @@ 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.
+ 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 passphraseContraseña de base de datos incorrectaNo comment provided by engineer.
+
+ Wrong key or unknown connection - most likely this connection is deleted.
+ snd error text
+ Wrong passphrase!¡Contraseña incorrecta!
@@ -6986,17 +7093,17 @@ Los servidores de SimpleX no pueden ver tu perfil.
default (%@)
- por defecto (%@)
+ predeterminado (%@)pref valuedefault (no)
- por defecto (no)
+ predeterminado (no)No comment provided by engineer.default (yes)
- por defecto (sí)
+ predeterminado (sí)No comment provided by engineer.
@@ -7456,11 +7563,19 @@ Los servidores de SimpleX no pueden ver tu perfil.
desconocidoconnection info
+
+ unknown relays
+ No comment provided by engineer.
+ unknown statusestado desconocidoNo comment provided by engineer.
+
+ unprotected
+ No comment provided by engineer.
+ updated group profileha actualizado el perfil del grupo
@@ -7526,6 +7641,10 @@ Los servidores de SimpleX no pueden ver tu perfil.
semanastime unit
+
+ when IP hidden
+ No comment provided by engineer.
+ yessí
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..fbdb5dfe19 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 relayKäytä aina relettä
@@ -1040,6 +1048,10 @@
Tiedostoa ei voi vastaanottaaNo comment provided by engineer.
+
+ Capacity exceeded - recipient did not receive previously sent messages.
+ snd error text
+ CellularNo comment provided by engineer.
@@ -1863,6 +1875,10 @@ This cannot be undone!
Desktop devicesNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ DevelopKehitä
@@ -1966,11 +1982,19 @@ This cannot be undone!
Discover via local networkNo 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 laterTee myöhemmin
@@ -2509,7 +2533,7 @@ This cannot be undone!
Error: %@Virhe: %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2598,6 +2622,10 @@ This cannot be undone!
Tiedosto: %@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaTiedostot & media
@@ -2696,6 +2724,16 @@ This cannot be undone!
Forwarded fromNo comment provided by engineer.
+
+ Forwarding server: %1$@
+Destination server error: %2$@
+ snd error text
+
+
+ Forwarding server: %1$@
+Error: %2$@
+ snd error text
+ Found desktopNo comment provided by engineer.
@@ -3480,6 +3518,10 @@ This is your link for group %@!
Viestien toimituskuittaukset!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftViestiluonnos
@@ -3500,6 +3542,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 +3667,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 profilesUseita keskusteluprofiileja
@@ -3651,6 +3696,10 @@ This is your link for group %@!
Network connectionNo comment provided by engineer.
+
+ Network issues - message expired after many attempts to send it.
+ snd error text
+ Network managementNo comment provided by engineer.
@@ -4169,10 +4218,18 @@ Error: %@
Yksityiset tiedostonimetNo comment provided by engineer.
+
+ Private message routing
+ No comment provided by engineer.
+ Private notesname of notes to self
+
+ Private routing
+ No comment provided by engineer.
+ Profile and server connectionsProfiili- ja palvelinyhteydet
@@ -4249,6 +4306,10 @@ Error: %@
Estä ääniviestien lähettäminen.No comment provided by engineer.
+
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screenSuojaa sovellusnäyttö
@@ -4356,10 +4417,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.
@@ -4804,6 +4861,14 @@ Error: %@
Lähetä live-viestiNo 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 notificationsLähetys ilmoitukset
@@ -4908,6 +4973,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 passwordPalvelin vaatii valtuutuksen jonojen luomiseen, tarkista salasana
@@ -4923,6 +4992,10 @@ Error: %@
Palvelintesti epäonnistui!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ ServersPalvelimet
@@ -5038,11 +5111,19 @@ Error: %@
Näytä viimeiset viestitNo comment provided by engineer.
+
+ Show message status
+ No comment provided by engineer.
+ Show previewNäytä esikatseluNo comment provided by engineer.
+
+ Show → on messages sent via private routing.
+ No comment provided by engineer.
+ Show:Näytä:
@@ -5355,6 +5436,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 +5616,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 +5705,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 stateOdottamaton siirtotila
@@ -5666,6 +5750,10 @@ Sinua kehotetaan suorittamaan todennus loppuun, ennen kuin tämä ominaisuus ote
Tuntematon virheNo 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 +5894,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 serverKäytä palvelinta
@@ -6023,11 +6119,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 passphraseVäärä tietokannan tunnuslauseNo comment provided by engineer.
+
+ Wrong key or unknown connection - most likely this connection is deleted.
+ snd error text
+ Wrong passphrase!Väärä tunnuslause!
@@ -7142,10 +7250,18 @@ SimpleX-palvelimet eivät näe profiiliasi.
tuntematonconnection info
+
+ unknown relays
+ No comment provided by engineer.
+ unknown statusNo comment provided by engineer.
+
+ unprotected
+ No comment provided by engineer.
+ updated group profilepäivitetty ryhmäprofiili
@@ -7209,6 +7325,10 @@ SimpleX-palvelimet eivät näe profiiliasi.
viikkoatime unit
+
+ when IP hidden
+ No comment provided by engineer.
+ yeskyllä
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..81bd0f3741 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,10 @@
Autorise les messages éphémères seulement si votre contact vous l’autorise.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)Autoriser la suppression irréversible des messages uniquement si votre contact vous l'autorise. (24 heures)
@@ -808,6 +812,10 @@
Groupe déjà rejoint !No comment provided by engineer.
+
+ Always use private routing.
+ No comment provided by engineer.
+ Always use relaySe connecter via relais
@@ -1088,6 +1096,10 @@
Impossible de recevoir le fichierNo comment provided by engineer.
+
+ Capacity exceeded - recipient did not receive previously sent messages.
+ snd error text
+ CellularCellulaire
@@ -1925,7 +1937,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 +1960,10 @@ Cette opération ne peut être annulée !
Appareils de bureauNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ DevelopDévelopper
@@ -2053,11 +2069,19 @@ Cette opération ne peut être annulée !
Rechercher sur le réseauNo 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.N'utilisez PAS SimpleX pour les appels d'urgence.No comment provided by engineer.
+
+ Do NOT use private routing.
+ No comment provided by engineer.
+ Do it laterFaites-le plus tard
@@ -2621,7 +2645,7 @@ Cette opération ne peut être annulée !
Error: %@Erreur : %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2713,6 +2737,10 @@ Cette opération ne peut être annulée !
Fichier : %@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaFichiers & médias
@@ -2775,7 +2803,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 +2846,16 @@ Cette opération ne peut être annulée !
Transféré depuisNo comment provided by engineer.
+
+ Forwarding server: %1$@
+Destination server error: %2$@
+ snd error text
+
+
+ Forwarding server: %1$@
+Error: %2$@
+ snd error text
+ Found desktopBureau trouvé
@@ -3633,6 +3671,10 @@ Voici votre lien pour le groupe %@ !
Accusés de réception des messages !No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftBrouillon de message
@@ -3653,6 +3695,14 @@ Voici votre lien pour le groupe %@ !
Les réactions aux messages sont interdites dans ce groupe.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.La source du message reste privée.
@@ -3783,11 +3833,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 profilesDifférents profils de chat
@@ -3818,6 +3863,10 @@ Voici votre lien pour le groupe %@ !
Connexion au réseauNo comment provided by engineer.
+
+ Network issues - message expired after many attempts to send it.
+ snd error text
+ Network managementGestion du réseau
@@ -4359,11 +4408,19 @@ Erreur : %@
Noms de fichiers privésNo comment provided by engineer.
+
+ Private message routing
+ No comment provided by engineer.
+ Private notesNotes privéesname of notes to self
+
+ Private routing
+ No comment provided by engineer.
+ Profile and server connectionsProfil et connexions au serveur
@@ -4444,6 +4501,10 @@ Erreur : %@
Interdire l'envoi de messages vocaux.No comment provided by engineer.
+
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screenProtéger l'écran de l'app
@@ -4554,11 +4615,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 +4647,7 @@ Erreur : %@
Reconnect servers?
- Reconnecter les serveurs?
+ Reconnecter les serveurs ?No comment provided by engineer.
@@ -4666,7 +4722,7 @@ Erreur : %@
Renegotiate encryption?
- Renégocier le chiffrement?
+ Renégocier le chiffrement ?No comment provided by engineer.
@@ -5019,6 +5075,14 @@ Erreur : %@
Envoyer un message dynamiqueNo 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 notificationsEnvoi de notifications
@@ -5124,6 +5188,10 @@ 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.
+ srv error text.
+ Server requires authorization to create queues, check passwordLe serveur requiert une autorisation pour créer des files d'attente, vérifiez le mot de passe
@@ -5139,6 +5207,10 @@ Erreur : %@
Échec du test du serveur !No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ ServersServeurs
@@ -5259,11 +5331,19 @@ Erreur : %@
Voir les derniers messagesNo comment provided by engineer.
+
+ Show message status
+ No comment provided by engineer.
+ Show previewAfficher l'aperçuNo comment provided by engineer.
+
+ Show → on messages sent via private routing.
+ No comment provided by engineer.
+ Show:Afficher :
@@ -5586,6 +5666,10 @@ Cela peut se produire en raison d'un bug ou lorsque la connexion est compromise.
L'application peut vous avertir lorsque vous recevez des messages ou des demandes de contact - veuillez ouvrir les paramètres pour les activer.No comment provided by engineer.
+
+ The app 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.La tentative de modification de la phrase secrète de la base de données n'a pas abouti.
@@ -5771,6 +5855,10 @@ 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.
+ 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 +5951,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 +5996,10 @@ Vous serez invité à confirmer l'authentification avant que cette fonction ne s
Erreur inconnueNo 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.À moins que vous utilisiez l'interface d'appel d'iOS, activez le mode "Ne pas déranger" pour éviter les interruptions.
@@ -6060,6 +6147,14 @@ 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.
+ No comment provided by engineer.
+
+
+ Use private routing with unknown servers.
+ No comment provided by engineer.
+ Use serverUtiliser ce serveur
@@ -6212,7 +6307,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 messagesNo comment provided by engineer.
@@ -6295,11 +6390,23 @@ 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.
+ 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 passphraseMauvaise phrase secrète pour la base de donnéesNo comment provided by engineer.
+
+ Wrong key or unknown connection - most likely this connection is deleted.
+ snd error text
+ Wrong passphrase!Mauvaise phrase secrète !
@@ -7455,11 +7562,19 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.
inconnuconnection info
+
+ unknown relays
+ No comment provided by engineer.
+ unknown statusstatut inconnuNo comment provided by engineer.
+
+ unprotected
+ No comment provided by engineer.
+ updated group profilemise à jour du profil de groupe
@@ -7525,6 +7640,10 @@ Les serveurs SimpleX ne peuvent pas voir votre profil.
semainestime unit
+
+ when IP hidden
+ No comment provided by engineer.
+ yesoui
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..bd8b71fe65 100644
--- a/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff
+++ b/apps/ios/SimpleX Localizations/hu.xcloc/Localized Contents/hu.xliff
@@ -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,26 @@
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 downgradeNo 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 +739,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 +764,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 +774,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 +812,10 @@
A csatlakozás folyamatban van a csoporthoz!No comment provided by engineer.
+
+ Always use private routing.
+ No comment provided by engineer.
+ Always use relayMindig használjon átjátszó kiszolgálót
@@ -905,12 +913,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 visszavonvaPIN entry
@@ -935,7 +943,7 @@
Auto-accept contact requests
- Ismerős jelölések automatikus elfogadása
+ Kapcsolódási kérelmek automatikus elfogadásaNo comment provided by engineer.
@@ -995,7 +1003,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 +1028,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 +1096,10 @@
Nem lehet fogadni a fájltNo comment provided by engineer.
+
+ Capacity exceeded - recipient did not receive previously sent messages.
+ snd error text
+ CellularMobilhálózat
@@ -1465,7 +1477,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.
@@ -1738,7 +1750,7 @@ Ez az egyszer használatos hivatkozása!
Delete after
- Törlés miután
+ Törlés ennyi idő utánNo comment provided by engineer.
@@ -1865,7 +1877,7 @@ Ez a művelet nem vonható vissza!
Delete messages after
- Üzenetek törlése miután
+ Üzenetek törlése ennyi idő utánNo comment provided by engineer.
@@ -1948,6 +1960,10 @@ Ez a művelet nem vonható vissza!
SzámítógépekNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ DevelopFejlesztés
@@ -1985,7 +2001,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 +2031,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 +2069,19 @@ Ez a művelet nem vonható vissza!
Felfedezés helyi hálózaton keresztülNo 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.NE használja a SimpleX-et segélyhívásokhoz.No comment provided by engineer.
+
+ Do NOT use private routing.
+ No comment provided by engineer.
+ Do it laterKésőbbre halaszt
@@ -2621,7 +2645,7 @@ Ez a művelet nem vonható vissza!
Error: %@Hiba: %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2713,6 +2737,10 @@ Ez a művelet nem vonható vissza!
Fájl: %@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaFájlok és média
@@ -2818,6 +2846,16 @@ Ez a művelet nem vonható vissza!
Továbbítva innen:No comment provided by engineer.
+
+ Forwarding server: %1$@
+Destination server error: %2$@
+ snd error text
+
+
+ Forwarding server: %1$@
+Error: %2$@
+ snd error text
+ Found desktopMegtalált számítógép
@@ -3090,7 +3128,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 +3375,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 +3508,7 @@ Ez az ön hivatkozása a(z) %@ csoporthoz!
Leave
- Elhagy
+ ElhagyásNo comment provided by engineer.
@@ -3585,12 +3623,12 @@ Ez az ön hivatkozása a(z) %@ csoporthoz!
Mark read
- Megjelölés olvasottként
+ Olvasottként jelölésNo comment provided by engineer.Mark verified
- Ellenőrzöttként jelölve
+ HitelesítésNo comment provided by engineer.
@@ -3633,6 +3671,10 @@ Ez az ön hivatkozása a(z) %@ csoporthoz!
Üzenetkézbesítési bizonylatok!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftÜzenetvázlat
@@ -3645,12 +3687,20 @@ 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
+ No comment provided by engineer.
+
+
+ Message routing modeNo comment provided by engineer.
@@ -3783,11 +3833,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 profilesTöbb csevegőprofil
@@ -3818,6 +3863,10 @@ Ez az ön hivatkozása a(z) %@ csoporthoz!
InternetkapcsolatNo comment provided by engineer.
+
+ Network issues - message expired after many attempts to send it.
+ snd error text
+ Network managementHálózatkezelés
@@ -4079,7 +4128,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 +4408,19 @@ Hiba: %@
Privát fájl nevekNo comment provided by engineer.
+
+ Private message routing
+ No comment provided by engineer.
+ Private notesPrivát jegyzetekname of notes to self
+
+ Private routing
+ No comment provided by engineer.
+ Profile and server connectionsProfil és kiszolgálókapcsolatok
@@ -4401,7 +4458,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 +4468,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 +4483,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 +4498,11 @@ 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 addressNo comment provided by engineer.
@@ -4554,11 +4615,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 +4697,12 @@ Hiba: %@
Remove member
- Tag eltávolítása
+ EltávolításNo comment provided by engineer.Remove member?
- Tag eltávolítása?
+ Biztosan eltávolítja?No comment provided by engineer.
@@ -4911,7 +4967,7 @@ Hiba: %@
Scan code
- Kód beolvasása
+ BeolvasásNo comment provided by engineer.
@@ -5019,6 +5075,14 @@ Hiba: %@
Élő üzenet küldéseNo 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Értesítések küldése
@@ -5051,7 +5115,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 +5188,10 @@ 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.
+ srv error text.
+ Server requires authorization to create queues, check passwordA kiszolgálónak engedélyre van szüksége a várólisták létrehozásához, ellenőrizze jelszavát
@@ -5139,6 +5207,10 @@ Hiba: %@
Sikertelen kiszolgáló-teszt!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ ServersKiszolgálók
@@ -5259,11 +5331,19 @@ Hiba: %@
Utolsó üzenetek megjelenítéseNo comment provided by engineer.
+
+ Show message status
+ No comment provided by engineer.
+ Show previewElőnézet megjelenítéseNo comment provided by engineer.
+
+ Show → on messages sent via private routing.
+ No comment provided by engineer.
+ Show:Megjelenítés:
@@ -5326,7 +5406,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 +5666,10 @@ 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).
+ 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 +5682,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 +5855,10 @@ 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.
+ 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 +5951,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 stateVáratlan átköltöztetési állapot
@@ -5913,6 +5996,10 @@ A funkció engedélyezése előtt a rendszer felszólítja a hitelesítés befej
Ismeretlen hibaNo 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.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.
@@ -6060,6 +6147,14 @@ 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.
+ No comment provided by engineer.
+
+
+ Use private routing with unknown servers.
+ No comment provided by engineer.
+ Use serverKiszolgáló használata
@@ -6167,7 +6262,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 +6390,23 @@ 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.
+ 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 passphraseTéves adatbázis jelmondatNo comment provided by engineer.
+
+ Wrong key or unknown connection - most likely this connection is deleted.
+ snd error text
+ Wrong passphrase!Téves jelmondat!
@@ -6642,8 +6749,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 +6957,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.
cancelled %@
- %@ törölve
+ %@ visszavonvafeature offered item
@@ -6965,7 +7072,7 @@ A SimpleX kiszolgálók nem látjhatják profilját.
creator
- szerző
+ készítőNo comment provided by engineer.
@@ -7185,12 +7292,12 @@ A SimpleX kiszolgálók nem látjhatják profilját.
invited
- meghívta
+ meghívaNo comment provided by engineer.invited %@
- meghívta %@-t
+ meghívta őt: %@rcv group event chat item
@@ -7200,7 +7307,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ülrcv group event chat item
@@ -7455,14 +7562,22 @@ A SimpleX kiszolgálók nem látjhatják profilját.
ismeretlenconnection info
+
+ unknown relays
+ No comment provided by engineer.
+ unknown statusismeretlen státuszNo comment provided by engineer.
+
+ unprotected
+ No comment provided by engineer.
+ updated group profile
- módosított csoport profil
+ frissítette a csoport profiljátrcv group event chat item
@@ -7507,7 +7622,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 +7640,10 @@ A SimpleX kiszolgálók nem látjhatják profilját.
héttime unit
+
+ when IP hidden
+ No comment provided by engineer.
+ yesigen
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..dda9effd4c 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,10 @@
Consenti i messaggi a tempo solo se il contatto li consente a te.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)Consenti l'eliminazione irreversibile dei messaggi solo se il contatto la consente a te. (24 ore)
@@ -808,6 +812,10 @@
Già in ingresso nel gruppo!No comment provided by engineer.
+
+ Always use private routing.
+ No comment provided by engineer.
+ Always use relayConnetti via relay
@@ -1088,6 +1096,10 @@
Impossibile ricevere il fileNo comment provided by engineer.
+
+ Capacity exceeded - recipient did not receive previously sent messages.
+ snd error text
+ CellularMobile
@@ -1948,6 +1960,10 @@ Non è reversibile!
Dispositivi desktopNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ DevelopSviluppa
@@ -2053,11 +2069,19 @@ Non è reversibile!
Individua via rete localeNo 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.NON usare SimpleX per chiamate di emergenza.No comment provided by engineer.
+
+ Do NOT use private routing.
+ No comment provided by engineer.
+ Do it laterFallo dopo
@@ -2621,7 +2645,7 @@ Non è reversibile!
Error: %@Errore: %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2713,6 +2737,10 @@ Non è reversibile!
File: %@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaFile e multimediali
@@ -2818,6 +2846,16 @@ Non è reversibile!
Inoltrato daNo comment provided by engineer.
+
+ Forwarding server: %1$@
+Destination server error: %2$@
+ snd error text
+
+
+ Forwarding server: %1$@
+Error: %2$@
+ snd error text
+ Found desktopDesktop trovato
@@ -3633,6 +3671,10 @@ Questo è il tuo link per il gruppo %@!
Ricevute di consegna dei messaggi!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftBozza dei messaggi
@@ -3653,6 +3695,14 @@ Questo è il tuo link per il gruppo %@!
Le reazioni ai messaggi sono vietate in questo gruppo.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.La fonte del messaggio resta privata.
@@ -3783,11 +3833,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 profilesProfili di chat multipli
@@ -3818,6 +3863,10 @@ Questo è il tuo link per il gruppo %@!
Connessione di reteNo comment provided by engineer.
+
+ Network issues - message expired after many attempts to send it.
+ snd error text
+ Network managementGestione della rete
@@ -4359,11 +4408,19 @@ Errore: %@
Nomi di file privatiNo comment provided by engineer.
+
+ Private message routing
+ No comment provided by engineer.
+ Private notesNote privatename of notes to self
+
+ Private routing
+ No comment provided by engineer.
+ Profile and server connectionsProfilo e connessioni al server
@@ -4444,6 +4501,10 @@ Errore: %@
Proibisci l'invio di messaggi vocali.No comment provided by engineer.
+
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screenProteggi la schermata dell'app
@@ -4554,11 +4615,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.
@@ -5019,6 +5075,14 @@ Errore: %@
Invia messaggio in direttaNo 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 notificationsInvia notifiche
@@ -5124,6 +5188,10 @@ Errore: %@
I messaggi inviati verranno eliminati dopo il tempo impostato.No comment provided by engineer.
+
+ Server address is incompatible with network settings.
+ srv error text.
+ Server requires authorization to create queues, check passwordIl server richiede l'autorizzazione di creare code, controlla la password
@@ -5139,6 +5207,10 @@ Errore: %@
Test del server fallito!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ ServersServer
@@ -5259,11 +5331,19 @@ Errore: %@
Mostra ultimi messaggiNo comment provided by engineer.
+
+ Show message status
+ No comment provided by engineer.
+ Show previewMostra anteprimaNo comment provided by engineer.
+
+ Show → on messages sent via private routing.
+ No comment provided by engineer.
+ Show:Mostra:
@@ -5586,6 +5666,10 @@ Può accadere a causa di qualche bug o quando la connessione è compromessa.L'app può avvisarti quando ricevi messaggi o richieste di contatto: apri le impostazioni per attivare.
No comment provided by engineer.
+
+ The app 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.Il tentativo di cambiare la password del database non è stato completato.
@@ -5771,6 +5855,10 @@ 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.
+ 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 +5951,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 stateStato di migrazione imprevisto
@@ -5913,6 +5996,10 @@ Ti verrà chiesto di completare l'autenticazione prima di attivare questa funzio
Errore sconosciutoNo 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.A meno che non utilizzi l'interfaccia di chiamata iOS, attiva la modalità Non disturbare per evitare interruzioni.
@@ -6060,6 +6147,14 @@ 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.
+ No comment provided by engineer.
+
+
+ Use private routing with unknown servers.
+ No comment provided by engineer.
+ Use serverUsa il server
@@ -6295,11 +6390,23 @@ 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.
+ 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 passphrasePassword del database sbagliataNo comment provided by engineer.
+
+ Wrong key or unknown connection - most likely this connection is deleted.
+ snd error text
+ Wrong passphrase!Password sbagliata!
@@ -7455,11 +7562,19 @@ I server di SimpleX non possono vedere il tuo profilo.
sconosciutoconnection info
+
+ unknown relays
+ No comment provided by engineer.
+ unknown statusstato sconosciutoNo comment provided by engineer.
+
+ unprotected
+ No comment provided by engineer.
+ updated group profileha aggiornato il profilo del gruppo
@@ -7525,6 +7640,10 @@ I server di SimpleX non possono vedere il tuo profilo.
settimanetime unit
+
+ when IP hidden
+ No comment provided by engineer.
+ yessì
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..35afbf2827 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
+ CellularNo comment provided by engineer.
@@ -1887,6 +1899,10 @@ This cannot be undone!
Desktop devicesNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ Develop開発
@@ -1990,11 +2006,19 @@ This cannot be undone!
Discover via local networkNo 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 +2558,7 @@ This cannot be undone!
Error: %@エラー : %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2623,6 +2647,10 @@ This cannot be undone!
ファイル: %@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaファイルとメディア
@@ -2721,6 +2749,16 @@ This cannot be undone!
Forwarded fromNo comment provided by engineer.
+
+ Forwarding server: %1$@
+Destination server error: %2$@
+ snd error text
+
+
+ Forwarding server: %1$@
+Error: %2$@
+ snd error text
+ Found desktopNo comment provided by engineer.
@@ -3504,6 +3542,10 @@ This is your link for group %@!
Message delivery receipts!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftメッセージの下書き
@@ -3524,6 +3566,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 +3691,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 +3720,10 @@ This is your link for group %@!
Network connectionNo comment provided by engineer.
+
+ Network issues - message expired after many attempts to send it.
+ snd error text
+ Network managementNo comment provided by engineer.
@@ -4195,10 +4244,18 @@ Error: %@
プライベートなファイル名No comment provided by engineer.
+
+ Private message routing
+ No comment provided by engineer.
+ Private notesname of notes to self
+
+ Private routing
+ No comment provided by engineer.
+ Profile and server connectionsプロフィールとサーバ接続
@@ -4275,6 +4332,10 @@ Error: %@
音声メッセージを使用禁止にする。No comment provided by engineer.
+
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screenアプリ画面を守る
@@ -4381,10 +4442,6 @@ Error: %@
開発中の機能です!相手のクライアントが4.2でなければ機能しません。アドレス変更が完了すると、会話にメッセージが出ます。連絡相手 (またはグループのメンバー) からメッセージを受信できないかをご確認ください。No comment provided by engineer.
-
- Receiving concurrency
- No comment provided by engineer.
- Receiving file will be stopped.ファイルの受信を停止します。
@@ -4829,6 +4886,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 +4991,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 +5010,10 @@ Error: %@
サーバテスト失敗!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ Serversサーバ
@@ -5056,11 +5129,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 +5455,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 +5634,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 +5723,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 +5768,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 +5912,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 +6137,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!パスフレーズが違います!
@@ -7160,10 +7268,18 @@ SimpleX サーバーはあなたのプロファイルを参照できません。
不明connection info
+
+ unknown relays
+ No comment provided by engineer.
+ unknown statusNo comment provided by engineer.
+
+ unprotected
+ No comment provided by engineer.
+ updated group profileグループプロフィールを更新しました
@@ -7227,6 +7343,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..7e3775188b 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,10 @@
Sta verdwijnende berichten alleen toe als uw contact dit toestaat.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)Sta het onomkeerbaar verwijderen van berichten alleen toe als uw contact dit toestaat. (24 uur)
@@ -808,6 +812,10 @@
Al lid van de groep!No comment provided by engineer.
+
+ Always use private routing.
+ No comment provided by engineer.
+ Always use relayAltijd relay gebruiken
@@ -1088,6 +1096,10 @@
Kan bestand niet ontvangenNo comment provided by engineer.
+
+ Capacity exceeded - recipient did not receive previously sent messages.
+ snd error text
+ CellularMobiel
@@ -1948,6 +1960,10 @@ Dit kan niet ongedaan gemaakt worden!
Desktop apparatenNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ DevelopOntwikkelen
@@ -2053,11 +2069,19 @@ Dit kan niet ongedaan gemaakt worden!
Ontdek via het lokale netwerkNo 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.Gebruik SimpleX NIET voor noodoproepen.No comment provided by engineer.
+
+ Do NOT use private routing.
+ No comment provided by engineer.
+ Do it laterDoe het later
@@ -2621,7 +2645,7 @@ Dit kan niet ongedaan gemaakt worden!
Error: %@Fout: %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2713,6 +2737,10 @@ Dit kan niet ongedaan gemaakt worden!
Bestand: %@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaBestanden en media
@@ -2818,6 +2846,16 @@ Dit kan niet ongedaan gemaakt worden!
Doorgestuurd vanuitNo comment provided by engineer.
+
+ Forwarding server: %1$@
+Destination server error: %2$@
+ snd error text
+
+
+ Forwarding server: %1$@
+Error: %2$@
+ snd error text
+ Found desktopDesktop gevonden
@@ -3633,6 +3671,10 @@ Dit is jouw link voor groep %@!
Ontvangst bevestiging voor berichten!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftConcept bericht
@@ -3653,6 +3695,14 @@ Dit is jouw link voor groep %@!
Reacties op berichten zijn verboden in deze groep.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.Berichtbron blijft privé.
@@ -3783,11 +3833,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 profilesMeerdere chat profielen
@@ -3818,6 +3863,10 @@ Dit is jouw link voor groep %@!
NetwerkverbindingNo comment provided by engineer.
+
+ Network issues - message expired after many attempts to send it.
+ snd error text
+ Network managementNetwerkbeheer
@@ -4359,11 +4408,19 @@ Fout: %@
Privé bestandsnamenNo comment provided by engineer.
+
+ Private message routing
+ No comment provided by engineer.
+ Private notesPrivé notitiesname of notes to self
+
+ Private routing
+ No comment provided by engineer.
+ Profile and server connectionsProfiel- en serververbindingen
@@ -4444,6 +4501,10 @@ Fout: %@
Verbieden het verzenden van spraak berichten.No comment provided by engineer.
+
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screenApp scherm verbergen
@@ -4554,11 +4615,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.
@@ -5019,6 +5075,14 @@ Fout: %@
Stuur een liveberichtNo 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 notificationsMeldingen verzenden
@@ -5124,6 +5188,10 @@ Fout: %@
Verzonden berichten worden na ingestelde tijd verwijderd.No comment provided by engineer.
+
+ Server address is incompatible with network settings.
+ srv error text.
+ Server requires authorization to create queues, check passwordServer vereist autorisatie om wachtrijen te maken, controleer wachtwoord
@@ -5139,6 +5207,10 @@ Fout: %@
Servertest mislukt!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ ServersServers
@@ -5259,11 +5331,19 @@ Fout: %@
Laat laatste berichten zienNo comment provided by engineer.
+
+ Show message status
+ No comment provided by engineer.
+ Show previewToon voorbeeldNo comment provided by engineer.
+
+ Show → on messages sent via private routing.
+ No comment provided by engineer.
+ Show:Toon:
@@ -5586,6 +5666,10 @@ Het kan gebeuren vanwege een bug of wanneer de verbinding is aangetast.
De app kan u op de hoogte stellen wanneer u berichten of contact verzoeken ontvangt - open de instellingen om dit in te schakelen.No comment provided by engineer.
+
+ The app 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.De poging om het wachtwoord van de database te wijzigen is niet voltooid.
@@ -5771,6 +5855,10 @@ 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.
+ 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 +5951,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 stateOnverwachte migratiestatus
@@ -5913,6 +5996,10 @@ U wordt gevraagd de authenticatie te voltooien voordat deze functie wordt ingesc
Onbekende foutNo 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.Schakel de modus Niet storen in om onderbrekingen te voorkomen, tenzij u de iOS-oproepinterface gebruikt.
@@ -6060,6 +6147,14 @@ 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.
+ No comment provided by engineer.
+
+
+ Use private routing with unknown servers.
+ No comment provided by engineer.
+ Use serverGebruik server
@@ -6295,11 +6390,23 @@ 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.
+ 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 passphraseVerkeerd wachtwoord voor de databaseNo comment provided by engineer.
+
+ Wrong key or unknown connection - most likely this connection is deleted.
+ snd error text
+ Wrong passphrase!Verkeerd wachtwoord!
@@ -7455,11 +7562,19 @@ SimpleX servers kunnen uw profiel niet zien.
onbekendconnection info
+
+ unknown relays
+ No comment provided by engineer.
+ unknown statusonbekende statusNo comment provided by engineer.
+
+ unprotected
+ No comment provided by engineer.
+ updated group profilebijgewerkt groep profiel
@@ -7525,6 +7640,10 @@ SimpleX servers kunnen uw profiel niet zien.
wekentime unit
+
+ when IP hidden
+ No comment provided by engineer.
+ yesJa
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..72add6a8ad 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,10 @@
Zezwól na znikające wiadomości tylko wtedy, gdy Twój kontakt Ci na to pozwoli.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)Zezwalaj na nieodwracalne usuwanie wiadomości tylko wtedy, gdy Twój kontakt Ci na to pozwoli. (24 godziny)
@@ -808,6 +812,10 @@
Już dołączono do grupy!No comment provided by engineer.
+
+ Always use private routing.
+ No comment provided by engineer.
+ Always use relayZawsze używaj przekaźnika
@@ -1088,6 +1096,10 @@
Nie można odebrać plikuNo comment provided by engineer.
+
+ Capacity exceeded - recipient did not receive previously sent messages.
+ snd error text
+ CellularSieć komórkowa
@@ -1948,6 +1960,10 @@ To nie może być cofnięte!
Urządzenia komputeroweNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ DevelopDeweloperskie
@@ -2053,11 +2069,19 @@ 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.
+ 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.
+ No comment provided by engineer.
+ Do it laterZrób to później
@@ -2621,7 +2645,7 @@ To nie może być cofnięte!
Error: %@Błąd: %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2713,6 +2737,10 @@ To nie może być cofnięte!
Plik: %@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaPliki i media
@@ -2818,6 +2846,16 @@ To nie może być cofnięte!
Przekazane dalej odNo comment provided by engineer.
+
+ Forwarding server: %1$@
+Destination server error: %2$@
+ snd error text
+
+
+ Forwarding server: %1$@
+Error: %2$@
+ snd error text
+ Found desktopZnaleziono komputer
@@ -3633,6 +3671,10 @@ To jest twój link do grupy %@!
Potwierdzenia dostarczenia wiadomości!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftWersja robocza wiadomości
@@ -3653,6 +3695,14 @@ To jest twój link do grupy %@!
Reakcje wiadomości są zabronione w tej grupie.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.Źródło wiadomości pozostaje prywatne.
@@ -3783,11 +3833,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 profilesWiele profili czatu
@@ -3818,6 +3863,10 @@ 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.
+ snd error text
+ Network managementZarządzenie sieciowe
@@ -4359,11 +4408,19 @@ Błąd: %@
Prywatne nazwy plikówNo comment provided by engineer.
+
+ Private message routing
+ No comment provided by engineer.
+ Private notesPrywatne notatkiname of notes to self
+
+ Private routing
+ No comment provided by engineer.
+ Profile and server connectionsProfil i połączenia z serwerem
@@ -4444,6 +4501,10 @@ Błąd: %@
Zabroń wysyłania wiadomości głosowych.No comment provided by engineer.
+
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screenChroń ekran aplikacji
@@ -4554,11 +4615,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.
@@ -5019,6 +5075,14 @@ Błąd: %@
Wyślij wiadomość na żywoNo 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 notificationsWyślij powiadomienia
@@ -5124,6 +5188,10 @@ Błąd: %@
Wysłane wiadomości zostaną usunięte po ustawionym czasie.No comment provided by engineer.
+
+ Server address is incompatible with network settings.
+ srv error text.
+ Server requires authorization to create queues, check passwordSerwer wymaga autoryzacji do tworzenia kolejek, sprawdź hasło
@@ -5139,6 +5207,10 @@ Błąd: %@
Test serwera nie powiódł się!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ ServersSerwery
@@ -5259,11 +5331,19 @@ Błąd: %@
Pokaż ostatnie wiadomościNo comment provided by engineer.
+
+ Show message status
+ No comment provided by engineer.
+ Show previewPokaż podglądNo comment provided by engineer.
+
+ Show → on messages sent via private routing.
+ No comment provided by engineer.
+ Show:Pokaż:
@@ -5586,6 +5666,10 @@ Może się to zdarzyć z powodu jakiegoś błędu lub gdy połączenie jest skom
Aplikacja może powiadamiać Cię, gdy otrzymujesz wiadomości lub prośby o kontakt — otwórz ustawienia, aby włączyć.No comment provided by engineer.
+
+ The app 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.Próba zmiany hasła bazy danych nie została zakończona.
@@ -5771,6 +5855,10 @@ 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.
+ 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 +5951,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 stateNieoczekiwany stan migracji
@@ -5913,6 +5996,10 @@ Przed włączeniem tej funkcji zostanie wyświetlony monit uwierzytelniania.Nieznany błąd
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.O ile nie korzystasz z interfejsu połączeń systemu iOS, włącz tryb Nie przeszkadzać, aby uniknąć przerywania.
@@ -6060,6 +6147,14 @@ 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.
+ No comment provided by engineer.
+
+
+ Use private routing with unknown servers.
+ No comment provided by engineer.
+ Use serverUżyj serwera
@@ -6295,11 +6390,23 @@ 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.
+ 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 passphraseNieprawidłowe hasło bazy danychNo comment provided by engineer.
+
+ Wrong key or unknown connection - most likely this connection is deleted.
+ snd error text
+ Wrong passphrase!Nieprawidłowe hasło!
@@ -7455,11 +7562,19 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.
nieznanyconnection info
+
+ unknown relays
+ No comment provided by engineer.
+ unknown statusnieznany statusNo comment provided by engineer.
+
+ unprotected
+ No comment provided by engineer.
+ updated group profilezaktualizowano profil grupy
@@ -7525,6 +7640,10 @@ Serwery SimpleX nie mogą zobaczyć Twojego profilu.
tygodnietime unit
+
+ when IP hidden
+ No comment provided by engineer.
+ yestak
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..c0a9df6b88 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,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Всегда соединяться через relay
@@ -1088,6 +1096,10 @@
Невозможно получить файлNo comment provided by engineer.
+
+ Capacity exceeded - recipient did not receive previously sent messages.
+ snd error text
+ CellularМобильная сеть
@@ -1948,6 +1960,10 @@ This cannot be undone!
КомпьютерыNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ DevelopДля разработчиков
@@ -2053,11 +2069,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 +2645,7 @@ This cannot be undone!
Error: %@Ошибка: %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2713,6 +2737,10 @@ This cannot be undone!
Файл: %@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaФайлы и медиа
@@ -2818,6 +2846,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,6 +3671,10 @@ This is your link for group %@!
Отчеты о доставке сообщений!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftЧерновик сообщения
@@ -3653,6 +3695,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 +3833,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 +3863,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 +4408,19 @@ Error: %@
Защищенные имена файлов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,6 +4501,10 @@ Error: %@
Запретить отправлять голосовые сообщений.No comment provided by engineer.
+
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screenЗащитить экран приложения
@@ -4554,11 +4615,6 @@ Error: %@
Адрес получения сообщений будет перемещён на другой сервер. Изменение адреса завершится после того как отправитель будет онлайн.No comment provided by engineer.
-
- Receiving concurrency
- Одновременный приём
- No comment provided by engineer.
- Receiving file will be stopped.Приём файла будет прекращён.
@@ -5019,6 +5075,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 +5188,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 +5207,10 @@ Error: %@
Ошибка теста сервера!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ ServersСерверы
@@ -5259,11 +5331,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 +5666,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 +5855,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 +5951,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 +5996,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 +6147,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 +6390,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!Неправильный пароль!
@@ -7455,11 +7562,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 +7640,10 @@ SimpleX серверы не могут получить доступ к Ваше
недельtime unit
+
+ when IP hidden
+ 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..c2a2d47bdc 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
+ CellularNo comment provided by engineer.
@@ -1851,6 +1863,10 @@ This cannot be undone!
Desktop devicesNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ Developพัฒนา
@@ -1953,11 +1969,19 @@ This cannot be undone!
Discover via local networkNo 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 +2518,7 @@ This cannot be undone!
Error: %@ข้อผิดพลาด: % @
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2583,6 +2607,10 @@ This cannot be undone!
ไฟล์: % @No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaไฟล์และสื่อ
@@ -2681,6 +2709,16 @@ This cannot be undone!
Forwarded fromNo comment provided by engineer.
+
+ Forwarding server: %1$@
+Destination server error: %2$@
+ snd error text
+
+
+ Forwarding server: %1$@
+Error: %2$@
+ snd error text
+ Found desktopNo comment provided by engineer.
@@ -3463,6 +3501,10 @@ This is your link for group %@!
ใบเสร็จการส่งข้อความ!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftร่างข้อความ
@@ -3483,6 +3525,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 +3649,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 +3678,10 @@ This is your link for group %@!
Network connectionNo comment provided by engineer.
+
+ Network issues - message expired after many attempts to send it.
+ snd error text
+ Network managementNo comment provided by engineer.
@@ -4150,10 +4199,18 @@ Error: %@
ชื่อไฟล์ส่วนตัวNo comment provided by engineer.
+
+ Private message routing
+ No comment provided by engineer.
+ Private notesname of notes to self
+
+ Private routing
+ No comment provided by engineer.
+ Profile and server connectionsการเชื่อมต่อโปรไฟล์และเซิร์ฟเวอร์
@@ -4230,6 +4287,10 @@ Error: %@
ห้ามส่งข้อความเสียงNo comment provided by engineer.
+
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screenปกป้องหน้าจอแอป
@@ -4336,10 +4397,6 @@ Error: %@
ที่อยู่ผู้รับจะถูกเปลี่ยนเป็นเซิร์ฟเวอร์อื่น การเปลี่ยนแปลงที่อยู่จะเสร็จสมบูรณ์หลังจากที่ผู้ส่งออนไลน์No comment provided by engineer.
-
- Receiving concurrency
- No comment provided by engineer.
- Receiving file will be stopped.การรับไฟล์จะหยุดลง
@@ -4783,6 +4840,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 +4950,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 +4969,10 @@ Error: %@
การทดสอบเซิร์ฟเวอร์ล้มเหลว!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ Serversเซิร์ฟเวอร์
@@ -5014,11 +5087,19 @@ Error: %@
Show last messagesNo 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 +5412,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 +5590,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 +5679,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 +5724,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 +5866,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 +6091,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!รหัสผ่านผิด!
@@ -7110,10 +7218,18 @@ SimpleX servers cannot see your profile.
ไม่ทราบconnection info
+
+ unknown relays
+ No comment provided by engineer.
+ unknown statusNo comment provided by engineer.
+
+ unprotected
+ No comment provided by engineer.
+ updated group profileอัปเดตโปรไฟล์กลุ่มแล้ว
@@ -7177,6 +7293,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..f0954e750f 100644
--- a/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff
+++ b/apps/ios/SimpleX Localizations/tr.xcloc/Localized Contents/tr.xliff
@@ -713,6 +713,10 @@
Eğer kişide izin verirse kaybolan mesajlara izin ver.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)Konuştuğun kişi, kalıcı olarak silinebilen mesajlara izin veriyorsa sen de ver. (24 saat içinde)
@@ -808,6 +812,10 @@
Zaten gruba bağlanılıyor!No comment provided by engineer.
+
+ Always use private routing.
+ No comment provided by engineer.
+ Always use relayHer zaman yönlendirici kullan
@@ -1088,6 +1096,10 @@
Dosya alınamıyorNo comment provided by engineer.
+
+ Capacity exceeded - recipient did not receive previously sent messages.
+ snd error text
+ CellularHücresel Veri
@@ -1948,6 +1960,10 @@ Bu geri alınamaz!
Bilgisayar cihazlarıNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ DevelopGeliştir
@@ -2053,11 +2069,19 @@ Bu geri alınamaz!
Yerel ağ aracılığıyla keşfetNo 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.Acil aramalar için SimpleX'i KULLANMAYIN.No comment provided by engineer.
+
+ Do NOT use private routing.
+ No comment provided by engineer.
+ Do it laterSonra yap
@@ -2621,7 +2645,7 @@ Bu geri alınamaz!
Error: %@Hata: %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2713,6 +2737,10 @@ Bu geri alınamaz!
Dosya: %@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaDosyalar & medya
@@ -2818,6 +2846,16 @@ Bu geri alınamaz!
Şuradan iletildiNo comment provided by engineer.
+
+ Forwarding server: %1$@
+Destination server error: %2$@
+ snd error text
+
+
+ Forwarding server: %1$@
+Error: %2$@
+ snd error text
+ Found desktopBilgisayar bulundu
@@ -3633,6 +3671,10 @@ Bu senin grup için bağlantın %@!
Mesaj alındı bilgisi!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftMesaj taslağı
@@ -3653,6 +3695,14 @@ Bu senin grup için bağlantın %@!
Mesaj tepkileri bu grupta yasaklandı.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.Mesaj kaynağı gizli kalır.
@@ -3783,11 +3833,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 +3863,10 @@ 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.
+ snd error text
+ Network managementAğ yönetimi
@@ -4359,11 +4408,19 @@ Hata: %@
Gizli dosya adlarıNo comment provided by engineer.
+
+ Private message routing
+ No comment provided by engineer.
+ Private notesGizli notlarname of notes to self
+
+ Private routing
+ No comment provided by engineer.
+ Profile and server connectionsProfil ve sunucu bağlantıları
@@ -4444,6 +4501,10 @@ Hata: %@
Sesli mesajların gönderimini yasakla.No comment provided by engineer.
+
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screenUygulama ekranını koru
@@ -4554,11 +4615,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.
@@ -5019,6 +5075,14 @@ Hata: %@
Canlı mesaj gönderNo 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 notificationsBildirimler gönder
@@ -5124,6 +5188,10 @@ Hata: %@
Gönderilen mesajlar ayarlanan süreden sonra silinecektir.No comment provided by engineer.
+
+ Server address is incompatible with network settings.
+ srv error text.
+ Server requires authorization to create queues, check passwordSunucunun sıra oluşturması için yetki gereklidir, şifreyi kontrol edin
@@ -5139,6 +5207,10 @@ Hata: %@
Sunucu testinde hata oluştu!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ ServersSunucular
@@ -5259,11 +5331,19 @@ Hata: %@
Son mesajları gösterNo comment provided by engineer.
+
+ Show message status
+ No comment provided by engineer.
+ Show previewÖn gösterimi göserNo comment provided by engineer.
+
+ Show → on messages sent via private routing.
+ No comment provided by engineer.
+ Show:Göster:
@@ -5586,6 +5666,10 @@ Bazı hatalar nedeniyle veya bağlantı tehlikeye girdiğinde meydana gelebilir.
Uygulama, mesaj veya iletişim isteği aldığınızda sizi bilgilendirebilir - etkinleştirmek için lütfen ayarları açın.No comment provided by engineer.
+
+ The app 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.Veritabanı parolasını değiştirme girişimi tamamlanmadı.
@@ -5771,6 +5855,10 @@ 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.
+ 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 +5951,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 stateBeklenmeyen geçiş durumu
@@ -5913,6 +5996,10 @@ Bu özellik etkinleştirilmeden önce kimlik doğrulamayı tamamlamanız istenec
Bilinmeyen hataNo 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 arama arayüzünü kullanmadığınız sürece, kesintileri önlemek için Rahatsız Etmeyin modunu etkinleştirin.
@@ -6060,6 +6147,14 @@ 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.
+ No comment provided by engineer.
+
+
+ Use private routing with unknown servers.
+ No comment provided by engineer.
+ Use serverSunucu kullan
@@ -6295,11 +6390,23 @@ 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.
+ 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 passphraseYanlış veritabanı parolasıNo comment provided by engineer.
+
+ Wrong key or unknown connection - most likely this connection is deleted.
+ snd error text
+ Wrong passphrase!Yanlış parola!
@@ -7455,11 +7562,19 @@ SimpleX sunucuları profilinizi göremez.
bilinmeyenconnection info
+
+ unknown relays
+ No comment provided by engineer.
+ unknown statusbilinmeyen durumNo comment provided by engineer.
+
+ unprotected
+ No comment provided by engineer.
+ updated group profilegrup profili güncellendi
@@ -7525,6 +7640,10 @@ SimpleX sunucuları profilinizi göremez.
haftalartime unit
+
+ when IP hidden
+ No comment provided by engineer.
+ yesevet
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..80686b6f5a 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,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 години)
@@ -807,6 +811,10 @@
Вже приєднуємося до групи!No comment provided by engineer.
+
+ Always use private routing.
+ No comment provided by engineer.
+ Always use relayЗавжди використовуйте реле
@@ -1087,6 +1095,10 @@
Не вдається отримати файлNo comment provided by engineer.
+
+ Capacity exceeded - recipient did not receive previously sent messages.
+ snd error text
+ CellularNo comment provided by engineer.
@@ -1946,6 +1958,10 @@ This cannot be undone!
Настільні пристроїNo comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ DevelopРозробник
@@ -2051,11 +2067,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Зробіть це пізніше
@@ -2617,7 +2641,7 @@ This cannot be undone!
Error: %@Помилка: %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2709,6 +2733,10 @@ This cannot be undone!
Файл: %@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & mediaФайли та медіа
@@ -2809,6 +2837,16 @@ This cannot be undone!
Forwarded fromNo comment provided by engineer.
+
+ Forwarding server: %1$@
+Destination server error: %2$@
+ snd error text
+
+
+ Forwarding server: %1$@
+Error: %2$@
+ snd error text
+ Found desktopЗнайдено робочий стіл
@@ -3622,6 +3660,10 @@ This is your link for group %@!
Підтвердження доставки повідомлення!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draftЧернетка повідомлення
@@ -3642,6 +3684,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.
@@ -3770,11 +3820,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Кілька профілів чату
@@ -3804,6 +3849,10 @@ This is your link for group %@!
Network connectionNo comment provided by engineer.
+
+ Network issues - message expired after many attempts to send it.
+ snd error text
+ Network managementNo comment provided by engineer.
@@ -4342,11 +4391,19 @@ Error: %@
Приватні імена файлів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З'єднання профілю та сервера
@@ -4425,6 +4482,10 @@ Error: %@
Заборонити надсилання голосових повідомлень.No comment provided by engineer.
+
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screenЗахистіть екран програми
@@ -4535,10 +4596,6 @@ Error: %@
Адреса отримувача буде змінена на інший сервер. Зміна адреси завершиться після того, як відправник з'явиться в мережі.No comment provided by engineer.
-
- Receiving concurrency
- No comment provided by engineer.
- Receiving file will be stopped.Отримання файлу буде зупинено.
@@ -4996,6 +5053,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Надсилати сповіщення
@@ -5101,6 +5166,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Сервер вимагає авторизації для створення черг, перевірте пароль
@@ -5116,6 +5185,10 @@ Error: %@
Тест сервера завершився невдало!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ ServersСервери
@@ -5235,11 +5308,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:Показати:
@@ -5559,6 +5640,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.Спроба змінити пароль до бази даних не була завершена.
@@ -5744,6 +5829,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.
@@ -5836,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Неочікуваний стан міграції
@@ -5886,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, увімкніть режим "Не турбувати", щоб уникнути переривань.
@@ -6033,6 +6121,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Використовувати сервер
@@ -6263,11 +6359,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!Неправильний пароль!
@@ -7417,11 +7525,19 @@ 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 +7603,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/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff b/apps/ios/SimpleX Localizations/zh-Hans.xcloc/Localized Contents/zh-Hans.xliff
index 4bf9e05665..edb01ac8ab 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.
@@ -1262,6 +1284,7 @@
Confirm network settings
+ 确认网络设置No comment provided by engineer.
@@ -1276,10 +1299,12 @@
Confirm that you remember database passphrase to migrate it.
+ 请在迁移前确认你记得数据库的密码短语。No comment provided by engineer.Confirm upload
+ 确认上传No comment provided by engineer.
@@ -1535,6 +1560,7 @@ This is your own one-time link!
Creating archive link
+ 正在创建存档链接No comment provided by engineer.
@@ -1756,6 +1782,7 @@ This cannot be undone!
Delete database from this device
+ 从这部设备上删除数据库No comment provided by engineer.
@@ -1907,6 +1934,10 @@ This cannot be undone!
桌面设备No comment provided by engineer.
+
+ Destination server error: %@
+ snd error text
+ Develop开发
@@ -2012,11 +2043,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 +2088,12 @@ This cannot be undone!
Download
+ 下载chat item actionDownload failed
+ 下载失败了No comment provided by engineer.
@@ -2062,10 +2103,12 @@ This cannot be undone!
Downloading archive
+ 正在下载存档No comment provided by engineer.Downloading link details
+ 正在下载链接详情No comment provided by engineer.
@@ -2125,6 +2168,7 @@ This cannot be undone!
Enable in direct chats (BETA)!
+ 在私聊中开启(公测)!No comment provided by engineer.
@@ -2159,6 +2203,7 @@ This cannot be undone!
Enabled for
+ 启用对象No comment provided by engineer.
@@ -2246,6 +2291,7 @@ This cannot be undone!
Enter passphrase
+ 输入密码短语No comment provided by engineer.
@@ -2399,6 +2445,7 @@ This cannot be undone!
Error downloading the archive
+ 下载存档出错No comment provided by engineer.
@@ -2477,6 +2524,7 @@ This cannot be undone!
Error saving settings
+ 保存设置出错when migrating
@@ -2550,10 +2598,12 @@ This cannot be undone!
Error uploading the archive
+ 上传存档出错No comment provided by engineer.Error verifying passphrase:
+ 验证密码短语出错:No comment provided by engineer.
@@ -2564,7 +2614,7 @@ This cannot be undone!
Error: %@错误: %@
- No comment provided by engineer.
+ snd error textError: URL is invalid
@@ -2608,6 +2658,7 @@ This cannot be undone!
Exported file doesn't exist
+ 导出的文件不存在No comment provided by engineer.
@@ -2655,6 +2706,10 @@ This cannot be undone!
文件:%@No comment provided by engineer.
+
+ Files
+ No comment provided by engineer.
+ Files & media文件和媒体
@@ -2672,6 +2727,7 @@ This cannot be undone!
Files and media not allowed
+ 不允许文件和媒体No comment provided by engineer.
@@ -2686,10 +2742,12 @@ This cannot be undone!
Finalize migration
+ 完成迁移No comment provided by engineer.Finalize migration on another device.
+ 在另一部设备上完成迁移No comment provided by engineer.
@@ -2739,20 +2797,34 @@ This cannot be undone!
Forward
+ 转发chat item actionForward 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 +2936,7 @@ This cannot be undone!
Group members can send SimpleX links.
+ 群成员可发送 SimpleX 链接。No comment provided by engineer.
@@ -3072,10 +3145,12 @@ This cannot be undone!
Import failed
+ 导入失败了No comment provided by engineer.Importing archive
+ 正在导入存档No comment provided by engineer.
@@ -3095,6 +3170,7 @@ This cannot be undone!
In order to continue, chat should be stopped.
+ 必须停止聊天才能继续。No comment provided by engineer.
@@ -3104,6 +3180,7 @@ This cannot be undone!
In-call sounds
+ 通话声音No comment provided by engineer.
@@ -3210,10 +3287,12 @@ This cannot be undone!
Invalid link
+ 无效链接No comment provided by engineer.Invalid migration confirmation
+ 迁移确认无效No comment provided by engineer.
@@ -3554,6 +3633,10 @@ This is your link for group %@!
消息送达回执!No comment provided by engineer.
+
+ Message delivery warning
+ item status text
+ Message draft消息草稿
@@ -3574,8 +3657,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 +3677,7 @@ This is your link for group %@!
Message too large
+ 消息太大了No comment provided by engineer.
@@ -3611,26 +3704,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 +3739,7 @@ This is your link for group %@!
Migration complete
+ 迁移完毕No comment provided by engineer.
@@ -3684,6 +3784,7 @@ This is your link for group %@!
More reliable network connection.
+ 更可靠的网络连接。No comment provided by engineer.
@@ -3691,11 +3792,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 +3819,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 +3943,7 @@ This is your link for group %@!
No network connection
+ 无网络连接No comment provided by engineer.
@@ -4037,6 +4140,7 @@ This is your link for group %@!
Or paste archive link
+ 或粘贴存档链接No comment provided by engineer.
@@ -4046,6 +4150,7 @@ This is your link for group %@!
Or securely share this file link
+ 或安全地分享此文件链接No comment provided by engineer.
@@ -4055,6 +4160,7 @@ This is your link for group %@!
Other
+ 其他No comment provided by engineer.
@@ -4138,6 +4244,7 @@ This is your link for group %@!
Picture-in-picture calls
+ 画中画通话No comment provided by engineer.
@@ -4162,6 +4269,7 @@ This is your link for group %@!
Please confirm that network settings are correct for this device.
+ 请确认网络设置对此这台设备正确无误。No comment provided by engineer.
@@ -4254,11 +4362,19 @@ Error: %@
私密文件名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 +4387,7 @@ Error: %@
Profile images
+ 个人资料图No comment provided by engineer.
@@ -4336,6 +4453,10 @@ Error: %@
禁止发送语音消息。No comment provided by engineer.
+
+ Protect IP address
+ No comment provided by engineer.
+ Protect app screen保护应用程序屏幕
@@ -4367,6 +4488,7 @@ Error: %@
Quantum resistant encryption
+ 抗量子加密No comment provided by engineer.
@@ -4444,10 +4566,6 @@ Error: %@
接收地址将变更到不同的服务器。地址更改将在发件人上线后完成。No comment provided by engineer.
-
- Receiving concurrency
- No comment provided by engineer.
- Receiving file will be stopped.即将停止接收文件。
@@ -4464,6 +4582,7 @@ Error: %@
Recipient(s) can't see who this message is from.
+ 收件人看不到这条消息来自何人。No comment provided by engineer.
@@ -4563,10 +4682,12 @@ Error: %@
Repeat download
+ 重复下载No comment provided by engineer.Repeat import
+ 重复导入No comment provided by engineer.
@@ -4576,6 +4697,7 @@ Error: %@
Repeat upload
+ 重复上传No comment provided by engineer.
@@ -4680,6 +4802,7 @@ Error: %@
Safer groups
+ 更安全的群组No comment provided by engineer.
@@ -4764,6 +4887,7 @@ Error: %@
Saved
+ 已保存No comment provided by engineer.
@@ -4773,6 +4897,7 @@ Error: %@
Saved from
+ 保存自No comment provided by engineer.
@@ -4900,6 +5025,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 +5138,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 +5157,10 @@ Error: %@
服务器测试失败!No comment provided by engineer.
+
+ Server version is incompatible with network settings.
+ srv error text
+ Servers服务器
@@ -5057,6 +5198,7 @@ Error: %@
Set passphrase
+ 设置密码短语No comment provided by engineer.
@@ -5081,6 +5223,7 @@ Error: %@
Shape profile images
+ 改变个人资料图形状No comment provided by engineer.
@@ -5120,6 +5263,7 @@ Error: %@
Show QR code
+ 显示二维码No comment provided by engineer.
@@ -5137,11 +5281,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 +5356,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 +5401,7 @@ Error: %@
Square, circle, or anything in between.
+ 方形、圆形、或两者之间的任意形状No comment provided by engineer.
@@ -5276,6 +5431,7 @@ Error: %@
Stop chat
+ 停止聊天程序No comment provided by engineer.
@@ -5320,6 +5476,7 @@ Error: %@
Stopping chat
+ 正在停止聊天No comment provided by engineer.
@@ -5459,6 +5616,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 +5732,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 itemThis chat is protected by quantum resistant end-to-end encryption.
+ 此聊天受抗量子的端到端加密保护。E2EE info chat item
@@ -5642,6 +5805,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 +5900,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 +5945,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 +6038,7 @@ To connect, please ask your contact to create another connection link and check
Upload failed
+ 上传失败了No comment provided by engineer.
@@ -5881,6 +6048,7 @@ To connect, please ask your contact to create another connection link and check
Uploading archive
+ 正在上传存档No comment provided by engineer.
@@ -5927,6 +6095,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 +6110,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 +6150,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 +6220,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 +6254,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 +6279,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 +6294,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 +6309,17 @@ To connect, please ask your contact to create another connection link and check
WiFi
+ WiFiNo comment provided by engineer.Will be enabled in direct chats!
+ 将在私聊中启用!No comment provided by engineer.Wired ethernet
+ 有线以太网No comment provided by engineer.
@@ -6151,11 +6337,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 +6456,7 @@ Repeat join request?
You can give another try.
+ 你可以再试一次。No comment provided by engineer.
@@ -6609,6 +6808,7 @@ SimpleX 服务器无法看到您的资料。
admins
+ 管理员feature role
@@ -6623,6 +6823,7 @@ SimpleX 服务器无法看到您的资料。
all members
+ 所有成员feature role
@@ -6955,6 +7156,7 @@ SimpleX 服务器无法看到您的资料。
forwarded
+ 已转发No comment provided by engineer.
@@ -7165,6 +7367,7 @@ SimpleX 服务器无法看到您的资料。
owners
+ 所有者feature role
@@ -7174,6 +7377,7 @@ SimpleX 服务器无法看到您的资料。
quantum resistant e2e encryption
+ 抗量子端到端加密chat item text
@@ -7218,6 +7422,7 @@ SimpleX 服务器无法看到您的资料。
saved
+ 已保存No comment provided by engineer.
@@ -7261,6 +7466,7 @@ SimpleX 服务器无法看到您的资料。
standard end-to-end encryption
+ 标准端到端加密chat item text
@@ -7287,11 +7493,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 +7570,10 @@ SimpleX 服务器无法看到您的资料。
周time unit
+
+ when IP hidden
+ No comment provided by engineer.
+ yes是
@@ -7363,6 +7581,7 @@ SimpleX 服务器无法看到您的资料。
you
+ 您No comment provided by engineer.
diff --git a/apps/ios/SimpleX.xcodeproj/project.pbxproj b/apps/ios/SimpleX.xcodeproj/project.pbxproj
index 8553109f13..22542016dd 100644
--- a/apps/ios/SimpleX.xcodeproj/project.pbxproj
+++ b/apps/ios/SimpleX.xcodeproj/project.pbxproj
@@ -76,11 +76,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 */; };
- 5C9F3DD62BFBCDD90003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DD12BFBCDD80003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf-ghc9.6.3.a */; };
- 5C9F3DD72BFBCDD90003B86B /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DD22BFBCDD80003B86B /* libffi.a */; };
- 5C9F3DD82BFBCDD90003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DD32BFBCDD80003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf.a */; };
- 5C9F3DD92BFBCDD90003B86B /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DD42BFBCDD90003B86B /* libgmpxx.a */; };
- 5C9F3DDA2BFBCDD90003B86B /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C9F3DD52BFBCDD90003B86B /* libgmp.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 */; };
@@ -144,6 +139,11 @@
5CEACCED27DEA495000BD591 /* MsgContentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEACCEC27DEA495000BD591 /* MsgContentView.swift */; };
5CEBD7462A5C0A8F00665FE2 /* KeyboardPadding.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */; };
5CEBD7482A5F115D00665FE2 /* SetDeliveryReceiptsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */; };
+ 5CEE87942C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE878F2C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a */; };
+ 5CEE87952C024F4F00583B8A /* libgmp.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE87902C024F4F00583B8A /* libgmp.a */; };
+ 5CEE87962C024F4F00583B8A /* libgmpxx.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE87912C024F4F00583B8A /* libgmpxx.a */; };
+ 5CEE87972C024F4F00583B8A /* libffi.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE87922C024F4F00583B8A /* libffi.a */; };
+ 5CEE87982C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CEE87932C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a */; };
5CF937202B24DE8C00E1D781 /* SharedFileSubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */; };
5CF937232B2503D000E1D781 /* NSESubscriber.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CF937212B25034A00E1D781 /* NSESubscriber.swift */; };
5CFA59C42860BC6200863A68 /* MigrateToAppGroupView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */; };
@@ -359,11 +359,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 = ""; };
- 5C9F3DD12BFBCDD80003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf-ghc9.6.3.a"; sourceTree = ""; };
- 5C9F3DD22BFBCDD80003B86B /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; };
- 5C9F3DD32BFBCDD80003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf.a"; sourceTree = ""; };
- 5C9F3DD42BFBCDD90003B86B /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; };
- 5C9F3DD52BFBCDD90003B86B /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.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 = ""; };
@@ -445,6 +440,11 @@
5CEACCEC27DEA495000BD591 /* MsgContentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MsgContentView.swift; sourceTree = ""; };
5CEBD7452A5C0A8F00665FE2 /* KeyboardPadding.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = KeyboardPadding.swift; sourceTree = ""; };
5CEBD7472A5F115D00665FE2 /* SetDeliveryReceiptsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SetDeliveryReceiptsView.swift; sourceTree = ""; };
+ 5CEE878F2C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a"; sourceTree = ""; };
+ 5CEE87902C024F4F00583B8A /* libgmp.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmp.a; sourceTree = ""; };
+ 5CEE87912C024F4F00583B8A /* libgmpxx.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libgmpxx.a; sourceTree = ""; };
+ 5CEE87922C024F4F00583B8A /* libffi.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = libffi.a; sourceTree = ""; };
+ 5CEE87932C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a */ = {isa = PBXFileReference; lastKnownFileType = archive.ar; path = "libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a"; sourceTree = ""; };
5CF9371F2B24DE8C00E1D781 /* SharedFileSubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SharedFileSubscriber.swift; sourceTree = ""; };
5CF937212B25034A00E1D781 /* NSESubscriber.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSESubscriber.swift; sourceTree = ""; };
5CFA59C32860BC6200863A68 /* MigrateToAppGroupView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MigrateToAppGroupView.swift; sourceTree = ""; };
@@ -539,13 +539,13 @@
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
files = (
- 5C9F3DD92BFBCDD90003B86B /* libgmpxx.a in Frameworks */,
- 5C9F3DDA2BFBCDD90003B86B /* libgmp.a in Frameworks */,
- 5C9F3DD82BFBCDD90003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf.a in Frameworks */,
+ 5CEE87942C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a in Frameworks */,
+ 5CEE87972C024F4F00583B8A /* libffi.a in Frameworks */,
+ 5CEE87962C024F4F00583B8A /* libgmpxx.a in Frameworks */,
5CE2BA93284534B000EC33A6 /* libiconv.tbd in Frameworks */,
- 5C9F3DD62BFBCDD90003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf-ghc9.6.3.a in Frameworks */,
+ 5CEE87952C024F4F00583B8A /* libgmp.a in Frameworks */,
5CE2BA94284534BB00EC33A6 /* libz.tbd in Frameworks */,
- 5C9F3DD72BFBCDD90003B86B /* libffi.a in Frameworks */,
+ 5CEE87982C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a in Frameworks */,
);
runOnlyForDeploymentPostprocessing = 0;
};
@@ -611,11 +611,11 @@
5C764E5C279C70B7000C6508 /* Libraries */ = {
isa = PBXGroup;
children = (
- 5C9F3DD22BFBCDD80003B86B /* libffi.a */,
- 5C9F3DD52BFBCDD90003B86B /* libgmp.a */,
- 5C9F3DD42BFBCDD90003B86B /* libgmpxx.a */,
- 5C9F3DD12BFBCDD80003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf-ghc9.6.3.a */,
- 5C9F3DD32BFBCDD80003B86B /* libHSsimplex-chat-5.8.0.2-8RkLdmy05dJBsgWrGV50Uf.a */,
+ 5CEE87922C024F4F00583B8A /* libffi.a */,
+ 5CEE87902C024F4F00583B8A /* libgmp.a */,
+ 5CEE87912C024F4F00583B8A /* libgmpxx.a */,
+ 5CEE878F2C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G-ghc9.6.3.a */,
+ 5CEE87932C024F4F00583B8A /* libHSsimplex-chat-5.8.0.3-3OkzEeUKATkEGDdUZR1g6G.a */,
);
path = Libraries;
sourceTree = "";
@@ -1562,7 +1562,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 218;
+ CURRENT_PROJECT_VERSION = 220;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -1611,7 +1611,7 @@
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_ENTITLEMENTS = "SimpleX (iOS).entitlements";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 218;
+ CURRENT_PROJECT_VERSION = 220;
DEAD_CODE_STRIPPING = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
@@ -1697,7 +1697,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 218;
+ CURRENT_PROJECT_VERSION = 220;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
GCC_OPTIMIZATION_LEVEL = s;
@@ -1734,7 +1734,7 @@
CODE_SIGN_ENTITLEMENTS = "SimpleX NSE/SimpleX NSE.entitlements";
CODE_SIGN_IDENTITY = "Apple Development";
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 218;
+ CURRENT_PROJECT_VERSION = 220;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
ENABLE_BITCODE = NO;
ENABLE_CODE_COVERAGE = NO;
@@ -1771,7 +1771,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 218;
+ CURRENT_PROJECT_VERSION = 220;
DEFINES_MODULE = YES;
DEVELOPMENT_TEAM = 5NN7GUYB6T;
DYLIB_COMPATIBILITY_VERSION = 1;
@@ -1822,7 +1822,7 @@
CLANG_TIDY_BUGPRONE_REDUNDANT_BRANCH_CONDITION = YES;
CLANG_TIDY_MISC_REDUNDANT_EXPRESSION = YES;
CODE_SIGN_STYLE = Automatic;
- CURRENT_PROJECT_VERSION = 218;
+ CURRENT_PROJECT_VERSION = 220;
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 7fa7e961ae..12683bc3a4 100644
--- a/apps/ios/SimpleXChat/APITypes.swift
+++ b/apps/ios/SimpleXChat/APITypes.swift
@@ -1743,7 +1743,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)
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..e6d98cc96a 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";
@@ -1779,7 +1779,7 @@
/* No comment provided by engineer. */
"Error: " = "Fehler: ";
-/* No comment provided by engineer. */
+/* snd error text */
"Error: %@" = "Fehler: %@";
/* No comment provided by engineer. */
@@ -2568,9 +2568,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";
@@ -2730,10 +2727,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.";
@@ -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." = "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.";
@@ -3920,9 +3914,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";
diff --git a/apps/ios/es.lproj/Localizable.strings b/apps/ios/es.lproj/Localizable.strings
index 8a61d6c438..42002cd282 100644
--- a/apps/ios/es.lproj/Localizable.strings
+++ b/apps/ios/es.lproj/Localizable.strings
@@ -675,7 +675,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!";
@@ -1162,13 +1162,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";
@@ -1492,7 +1492,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 +1779,7 @@
/* No comment provided by engineer. */
"Error: " = "Error: ";
-/* No comment provided by engineer. */
+/* snd error text */
"Error: %@" = "Error: %@";
/* No comment provided by engineer. */
@@ -2568,9 +2568,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";
@@ -2961,7 +2958,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";
@@ -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." = "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.";
@@ -3192,7 +3186,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";
@@ -3920,9 +3914,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";
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..ba34d03a78 100644
--- a/apps/ios/fr.lproj/Localizable.strings
+++ b/apps/ios/fr.lproj/Localizable.strings
@@ -1306,7 +1306,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";
@@ -1779,7 +1779,7 @@
/* No comment provided by engineer. */
"Error: " = "Erreur : ";
-/* No comment provided by engineer. */
+/* snd error text */
"Error: %@" = "Erreur : %@";
/* No comment provided by engineer. */
@@ -1876,7 +1876,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.";
@@ -2568,9 +2568,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";
@@ -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." = "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 +3093,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 +3156,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 ?";
@@ -3920,9 +3914,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";
@@ -4170,7 +4161,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 !";
diff --git a/apps/ios/hu.lproj/Localizable.strings b/apps/ios/hu.lproj/Localizable.strings
index b590785606..f7d1b35f4a 100644
--- a/apps/ios/hu.lproj/Localizable.strings
+++ b/apps/ios/hu.lproj/Localizable.strings
@@ -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,16 @@
"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 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." = "Üzenetreakciók engedélyezése kizárólag abban az esetben, ha ismerőse is engedélyezi.";
+"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 +462,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 +477,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?";
@@ -573,10 +573,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 +594,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 +636,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 +663,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 +708,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";
@@ -1006,7 +1006,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 +1075,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 +1183,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 +1261,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";
@@ -1348,7 +1348,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 +1369,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.";
@@ -1779,7 +1779,7 @@
/* No comment provided by engineer. */
"Error: " = "Hiba: ";
-/* No comment provided by engineer. */
+/* snd error text */
"Error: %@" = "Hiba: %@";
/* No comment provided by engineer. */
@@ -2077,7 +2077,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 +2245,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 +2266,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 +2344,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";
@@ -2419,10 +2419,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" = "Olvasottként 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";
@@ -2467,10 +2467,10 @@
"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";
@@ -2568,9 +2568,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";
@@ -2775,7 +2772,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.";
@@ -2976,22 +2973,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,7 +2997,7 @@
"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";
@@ -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." = "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 +3126,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?";
@@ -3306,7 +3300,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";
@@ -3411,7 +3405,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.";
@@ -3570,7 +3564,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";
@@ -3753,7 +3747,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!";
@@ -3920,9 +3914,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";
@@ -3999,7 +3990,7 @@
"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";
@@ -4137,7 +4128,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 +4140,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…";
@@ -4461,7 +4452,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..15bef7719e 100644
--- a/apps/ios/it.lproj/Localizable.strings
+++ b/apps/ios/it.lproj/Localizable.strings
@@ -1779,7 +1779,7 @@
/* No comment provided by engineer. */
"Error: " = "Errore: ";
-/* No comment provided by engineer. */
+/* snd error text */
"Error: %@" = "Errore: %@";
/* No comment provided by engineer. */
@@ -2568,9 +2568,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";
@@ -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." = "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.";
@@ -3920,9 +3914,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";
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..e19473fe4b 100644
--- a/apps/ios/nl.lproj/Localizable.strings
+++ b/apps/ios/nl.lproj/Localizable.strings
@@ -1779,7 +1779,7 @@
/* No comment provided by engineer. */
"Error: " = "Fout: ";
-/* No comment provided by engineer. */
+/* snd error text */
"Error: %@" = "Fout: %@";
/* No comment provided by engineer. */
@@ -2568,9 +2568,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";
@@ -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." = "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.";
@@ -3920,9 +3914,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";
diff --git a/apps/ios/pl.lproj/Localizable.strings b/apps/ios/pl.lproj/Localizable.strings
index 15262085eb..bd534a77e9 100644
--- a/apps/ios/pl.lproj/Localizable.strings
+++ b/apps/ios/pl.lproj/Localizable.strings
@@ -1779,7 +1779,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. */
@@ -2568,9 +2568,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";
@@ -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." = "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.";
@@ -3920,9 +3914,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";
diff --git a/apps/ios/ru.lproj/Localizable.strings b/apps/ios/ru.lproj/Localizable.strings
index 2c703f0095..baa9f89eac 100644
--- a/apps/ios/ru.lproj/Localizable.strings
+++ b/apps/ios/ru.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/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..0350dc836c 100644
--- a/apps/ios/tr.lproj/Localizable.strings
+++ b/apps/ios/tr.lproj/Localizable.strings
@@ -1779,7 +1779,7 @@
/* No comment provided by engineer. */
"Error: " = "Hata: ";
-/* No comment provided by engineer. */
+/* snd error text */
"Error: %@" = "Hata: %@";
/* No comment provided by engineer. */
@@ -2568,9 +2568,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";
@@ -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." = "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.";
@@ -3920,9 +3914,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";
diff --git a/apps/ios/uk.lproj/Localizable.strings b/apps/ios/uk.lproj/Localizable.strings
index d52efd2b65..488a2b1c82 100644
--- a/apps/ios/uk.lproj/Localizable.strings
+++ b/apps/ios/uk.lproj/Localizable.strings
@@ -1761,7 +1761,7 @@
/* No comment provided by engineer. */
"Error: " = "Помилка: ";
-/* No comment provided by engineer. */
+/* snd error text */
"Error: %@" = "Помилка: %@";
/* No comment provided by engineer. */
@@ -2520,9 +2520,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" = "Кілька профілів чату";
@@ -3821,9 +3818,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/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/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..b0557e2f67 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))
@@ -1166,6 +1175,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 +2460,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()
@@ -2507,6 +2529,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 +2598,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)}"
@@ -2656,6 +2678,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 +2744,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"
@@ -2790,6 +2812,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 +4085,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 +4104,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
@@ -4907,7 +4942,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 +5021,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 +5554,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 +5583,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 +5620,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 +5649,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 +5681,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(),
)
}
}
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..12b5747787 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,7 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
import kotlinx.datetime.Clock
+import java.io.File
@Composable
fun ChatInfoView(
@@ -337,6 +339,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)
// }
@@ -642,6 +654,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 +696,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 {
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..03d4e30a6c 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,24 @@ 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.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 +117,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 +545,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 +610,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.drawBehind { 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 +897,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 +1001,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 +1285,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 +1554,7 @@ fun PreviewChatLayout() {
acceptCall = { _ -> },
acceptFeature = { _, _, _ -> },
openDirectChat = { _ -> },
+ forwardItem = { _, _ -> },
updateContactStats = { },
updateMemberStats = { _, _ -> },
syncContactConnection = { },
@@ -1604,6 +1628,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/GroupChatInfoView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chat/group/GroupChatInfoView.kt
index c52dd941fd..69b4de6803 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.*
@@ -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/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/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..330003b743 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
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..89796baf4e
--- /dev/null
+++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/helpers/ChatWallpaper.kt
@@ -0,0 +1,417 @@
+package chat.simplex.common.views.helpers
+
+import androidx.compose.ui.geometry.Size
+import androidx.compose.ui.graphics.*
+import androidx.compose.ui.graphics.drawscope.DrawScope
+import androidx.compose.ui.graphics.drawscope.clipRect
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.unit.IntOffset
+import androidx.compose.ui.unit.IntSize
+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
+ }
+ }
+ }
+}
+
+fun DrawScope.chatViewBackground(image: ImageBitmap, imageType: WallpaperType, background: Color, tint: Color) = clipRect {
+ val quality = FilterQuality.High
+ fun repeat(imageScale: Float) {
+ 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
+ )
+ }
+ }
+ }
+
+ drawRect(background)
+ when (imageType) {
+ is WallpaperType.Preset -> repeat((imageType.scale ?: 1f) * imageType.predefinedImageScale)
+ is WallpaperType.Image -> when (val scaleType = imageType.scaleType ?: WallpaperScaleType.FILL) {
+ WallpaperScaleType.REPEAT -> repeat(imageType.scale ?: 1f)
+ WallpaperScaleType.FILL, WallpaperScaleType.FIT -> {
+ 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/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..f3a992d451 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
@@ -669,7 +669,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/usersettings/Appearance.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/usersettings/Appearance.kt
index 78e24e5e7e..1de2c7c489 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,429 @@ 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
+ .drawBehind {
+ if (wallpaperImage != null && wallpaperType != null && backgroundColor != null && tintColor != null) {
+ chatViewBackground(wallpaperImage, wallpaperType, backgroundColor, tintColor)
+ } else {
+ 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 +517,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 +529,8 @@ object AppearanceScope {
if (to != null) {
val theme = getThemeFromUri(to)
if (theme != null) {
- ThemeManager.saveAndApplyThemeOverrides(theme, isInDarkTheme)
+ ThemeManager.saveAndApplyThemeOverrides(theme)
+ saveThemeToDatabase(null)
}
}
}
@@ -184,49 +543,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`
@@ -254,7 +903,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 +915,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 +936,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 +958,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..d07fad8623 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
@@ -250,17 +250,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) } })
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..1a63ceb87b 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,73 @@
شكل الصور الشخصيةواجهة المستخدم الليتوانيةمربع أو دائرة أو أي شيء بينهما.
+ عنوان الخادم غير متوافق مع إعدادات الشبكة.
+ إصدار الخادم غير متوافق مع إعدادات الشبكة.
+ مفتاح خاطئ أو اتصال غير معروف - على الأرجح حُذف هذا الاتصال.
+ تم تجاوز السعة - لم يتلق المُستلم الرسائل المُرسلة مسبقًا.
+ خطأ في خادم الوجهة: %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
+ الإعدادات المتقدمة
+ جميع أوضاع الألوان
+ أسود
+ وضع اللون
+ داكن
+ الوضع الداكن
+ ألوان الوضع الداكن
+ ملائمة
+ طاب يومك!
+ صباح الخير!
+ صورة خلفية الشاشة
+ الوضع الفاتح
+ السمة الملف الشخصي
+ فاتح
+ طبّق لِ
+ ملء
+ المقياس
\ 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..35e3591aea 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.AppearanceCustomize theme
- THEME COLORS
+ INTERFACE COLORSApp versionApp version: v%sApp build: %s
@@ -874,6 +874,7 @@
SpeakerHeadphonesBluetooth
+ Error initializing WebView. Update your system to the new version. Please contact developers.\nError: %sThe next generation of private messaging
@@ -1072,6 +1073,9 @@
APP ICONTHEMESProfile images
+ Chat theme
+ Profile theme
+ Chat colorsMESSAGES AND FILESPRIVATE MESSAGE ROUTINGCALLS
@@ -1201,6 +1205,7 @@
Incompatible database versionConfirm database upgradesShow console in new window
+ Show chat list in new windowInvalid migration confirmationUpgrade and open chatDowngrade and open chat
@@ -1543,23 +1548,30 @@
When you share an incognito profile with somebody, this profile will be used for the groups they invite you to.
+ System
+ Light
+ DarkSystemLightDarkSimpleX
+ BlackSystemTheme
+ Color modeDark theme
- Save color
+ Dark mode colorsImport themeImport theme errorMake sure the file has correct YAML syntax. Export theme to have an example of the theme file structure.Export themeReset colors
+ Reset color
+ App themeAccentAdditional accentSecondary
@@ -1567,8 +1579,31 @@
BackgroundMenus & alertsTitle
+ Additional accent 2Sent message
+ Sent replyReceived 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
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ě\?InkognitoVáš náhodný profil
- Uložit barvuObnovit barvuZbarveníPovolujete
@@ -1770,4 +1769,6 @@
Kamera a mikrofonSimpleX 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..7bb6aaa128 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ügbarNeinErforderlich
- 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- & VideoanrufeIhre Anrufe
- Über ein Relais verbinden
+ Immer über ein Relais verbindenAnrufe auf Sperrbildschirm:AkzeptierenAnzeigen
@@ -842,7 +842,6 @@
DunkelDesign
- Farbe speichernFarben zurücksetzenAkzent
@@ -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-FARBENFü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.
@@ -1460,7 +1459,7 @@
Datenbank-Ordner öffnenDas 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ÖffnenGespeicherte Dateien & Medien verschlüsseln
@@ -1529,7 +1528,7 @@
FreigebenUngültiger Datei-PfadSie haben über diese Adresse bereits eine Verbindung beantragt!
- Die Konsole in einem neuen Fenster anzeigen
+ Konsole in einem neuen Fenster anzeigenVon %s werden alle neuen Nachrichten ausgeblendet!BlockiertFehler bei der Neuverhandlung der Verschlüsselung
@@ -1670,7 +1669,7 @@
Ehemaliges Mitglied %1$sDie Ausführung dieser Funktion dauert zu lange: %1$d Sekunden: %2$sLangsame Funktion
- Zeige langsame API-Aufrufe an
+ Langsame API-Aufrufe anzeigenunbekanntOptionen für Entwicklerunbekannter Gruppenmitglieds-Status
@@ -1729,7 +1728,7 @@
Link-Details werden heruntergeladenArchiv wird heruntergeladenAnwenden
- 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 HochladenWarnung: Das Archiv wird gelöscht.]]>Überprüfen Sie Ihre Internet-Verbindung und probieren Sie es nochmals
@@ -1850,4 +1849,73 @@
Profil-BilderForm der Profil-BilderQuadratisch, 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
+ Privates Routing mit unbekannten Servern nutzen.
+ Nutzen Sie 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
+ Herunterstufung erlauben
+ Immer privates Routing nutzen.
+ Senden Sie keine direkten Nachrichten, selbst wenn Ihr oder der Zielserver kein privates Routing unterstützt.
+ PRIVATES NACHRICHTEN-ROUTING
+ Nachrichten direkt versenden, wenn die IP-Adresse geschützt ist und Ihr oder der Zielserver kein privates Routing unterstützt.
+ Nachrichten direkt versenden, wenn Ihr oder der Zielserver kein privates Routing unterstützt.
+ Für die Auslieferung Ihrer Nachrichten wird privates Routing Ihrer SMP-Server genutzt, um Ihre IP-Adresse zu schützen.
+ Privates Routing mit unbekannten Servern nutzen, wenn die 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
\ 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..3bf05bf793 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 @@
conectadodirectaEl contacto permite
- predefinido (%s)
+ predeterminado (%s)Eliminar para todosactivadoTus contactos sólo pueden marcar los mensajes para eliminar. Tu podrás verlos.
@@ -274,7 +274,7 @@
Chat está paradorol de %s cambiado a %sCambiar rol
- Mediante perfil (por defecto) o por conexión (BETA)
+ Mediante perfil (predeterminado) o por conexión (BETA)cambiando de servidor…Preferencias de Chatcancelado %s
@@ -606,8 +606,7 @@
llamada rechazadasecretoAbrir SimpleX Chat para aceptar llamada
- Restablecer valores por defecto
- Guardar color
+ Restablecer valores predetarminadosPendienteNotificaciones periódicasGuarda la contraseña de forma segura, NO podrás cambiarla si la pierdes.
@@ -1162,7 +1161,7 @@
Mensaje enviadoDejar de compartir¿Dejar de compartir la dirección\?
- COLORES DEL TEMA
+ COLORES DEL INTERFAZPuedes crearla más tarde¿Compartir la dirección con los contactos\?Compartir con contactos
@@ -1510,7 +1509,7 @@
autorPegar dirección de ordenador%1$s!]]>
- Verificar código con ordenador
+ Verifica el código en el ordenadorEscanear código QR desde ordenadorDesbloquearDetectable mediante red local
@@ -1740,7 +1739,7 @@
WiFiEthernet por cableadministradores
- Activar para
+ Activado paraNo permitir el envío de enlaces SimpleXtodos los miembrosPermitir enviar enlaces SimpleX.
@@ -1767,7 +1766,76 @@
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 perfilesDar forma a las imágenes de perfilCuadrada, circular o cualquier forma intermedia.
+ Capacidad excedida - el destinatario no ha recibido el mensaje previo.
+ 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
+ Mostrar estado del mensaje
+ Usar enrutamiento privado con servidores desconocidos cuando la dirección IP no está protegida.
+ Con IP oculta
+ Si
+ Enviar los mensajes directamente cuando tu servidor o el de destino no soporten enrutamiento privado
+ Para proteger tu dirección IP, el enrutamiento privado usa tu servidor SMP para enviar mensajes.
+ NO enviar mensajes directos 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 los mensajes directamente cuando tu dirección IP está protegida y tu servidor o el de destino no soporten 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
\ 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..4e15bb27f8 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به مخاطبتاناطلاعات بیشتر
@@ -682,4 +682,1125 @@
یک نشانی ایجاد کنید تا اشخاص بتوانند به شما متصل شوند.نمایه شما روی دستگاهتان ذخیره شده و فقط با مخاطبانتان به اشتراک گذاشته میشود. سرورهای 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.
\ 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 salasanaOletusvärit
- Tallenna väriEstä ää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..2d279412cc 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 @@
ClairSombreThème
- Enregistrer la couleurRéinitialisation des couleursPrincipaleVous autorisez
@@ -1769,4 +1768,44 @@
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.
\ 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..401d426b79 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 TAG1 hónap1 hét
- 6 új felületi nyelv
+ 6 új kezelőfelületi nyelv5 perc1 percA SimpleX azonosítóról
@@ -26,7 +26,7 @@
Elfogadásgombra fent, majd:Elfogadás inkognítóban
- Kapcsolatfelvétel elfogadása?
+ Kapcsolódási kérelem elfogadása?ElfogadásElfogadásAzonosí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ásaHitelesítésEgy ü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 visszavonvaElő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: %sHang-/videóhívásokSpeciá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ásokAz 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ájltHitelesítés elérhetetlen
@@ -78,13 +78,13 @@
CímCsatlakozá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.VisszaKikapcsolható 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ásaFigyelem: 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ásaMinden 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ásadminFénykép előnézet visszavonásaA 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ásMég néhány dolog
- Hitelesítés megszakítva
+ Hitelesítés visszavonvaA 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ésMinden 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ásoktéves üzenet hash
- Mindig bekapcsolva
+ Mindig futAz 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ésAz 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öttJó 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 üzenetekA 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égetthívás folyamatbanFotó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 IKONKiszolgá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 kikapcsolvaA közvetlen üzenetek küldése a tagok számára engedélyezve van.AlkalmazásHívás folyamatbanMindké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ésMinden %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 bekapcsolvahanghívás (nem e2e titkosított)
@@ -200,7 +200,7 @@
az ismerős e2e titkosítással rendelkezikCsoport 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 általKapcsolódási hiba (AUTH)
@@ -291,7 +291,7 @@
ICE kiszolgálók beállításaCsoport törléseHitelesítés törlése
- szerző
+ készítőMegerősítésTörlés nálam%d üzenet törlése?
@@ -364,8 +364,8 @@
Eszközhitelesítés kikapcsolva. SimpleX zárolás kikapcsolása.LetiltásLetiltá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áraAz 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álasztvaEngedé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 percAz 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ózatonNe engedélyezzeArchí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 üzenetSzámítógép leválasztása?
@@ -462,7 +462,7 @@
%d fájl %s összméretbenAdatbá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áraKézbesítési jelentések kikapcsolva!KibontásHiba az üzenet küldésekor
@@ -521,7 +521,7 @@
Kísérleti funkciókEngedé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ásakorFájlA csoport tagjai küldhetnek fájlokat és médiatartalmakat.
- Törlés miután
+ Törlés ennyi idő utánHiba a beállítás megváltoztatásakorHiba a csoport hivatkozás frissítésekora 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épSzámítógépekA 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éseA 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ásNémításnincsenek 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.Nemnincs szövegTAG
@@ -754,7 +754,7 @@
Új kapcsolattartási kérelemCsatlakozá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ülelhagyta a csoportotÖsszekapcsolt számítógépekNincs alkalmazás jelkód
@@ -763,8 +763,8 @@
(csak a csoporttagok tárolják)Moderálásbe
- 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ő elCsak ö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ülA kapcsolódáshoz Onion kiszolgálókra lesz szükség.
@@ -785,7 +785,7 @@
Összekapcsolt mobil eszközökLehető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ásRendbenNincsenek 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
+ Olvasottként jelölésÉLŐ
- Olvasatlannak jelölve
+ Olvasatlannak jelölésTöbbBejelentkezés hitelesítő adatokkalérvénytelen üzenet formátum
@@ -847,7 +847,7 @@
Beszélgessünk a SimpleX Chat-benModerá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épeCsoport 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ásKé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ásaQR-kód beolvasása
@@ -1040,21 +1040,21 @@
Biztonsági kódAdja 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álataegyenrangúCSEVEGÉSI SZOLGÁLTATÁS INDÍTÁSAFogadott hivatkozás beillesztéseKiszolgá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átTÁMOGASSA A SIMPLEX CHATETSimpleX Chat szolgáltatásNem lehet üzeneteket küldeni!%s ellenőrzöttJelszó megjelenítéseAdatvédelem és biztonság
- Tag eltávolítása
+ EltávolításA jelkód beállítva!Elküldött üzenetIsmerősök kiválasztása
@@ -1078,7 +1078,7 @@
Ennek az eszköznek a neveJelenlegi profilFájl feltöltése
- Hang- és videóhívások tiltása.
+ A hívások kezdeményezése le van tiltva.MegköveteltSimpleX Chat üzenetekVisszaállítás
@@ -1097,7 +1097,7 @@
SimpleX egyszer használatos meghívóHívásoknem sikerült elküldeni
- TÉMA SZÍNEK
+ KEZELŐFELÜLET SZÍNEIVisszaállítElő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 hibaSaját kiszolgáló címCsevegés konzol megnyitása
- Tag eltávolítása
+ EltávolításAdatbázis jelmondat beállításaBiztonsági kód megtekintéseTag feloldása?
@@ -1143,7 +1143,7 @@
Csatlakozási kérés megismétlése?Képre várakozásHangüzenetek
- Tag eltávolítása?
+ Biztosan eltávolítja?Biztonsági kód ellenőrzéseeltávolítottakSimpleX azonosító
@@ -1158,12 +1158,12 @@
VáltozáslistaCsoport megnyitásaElküldve ekkor:
- Hangüzenetek küldése le van tiltva.
+ A hangüzenetek küldése le van tiltva.Utolsó üzenetek megjelenítéseAz előre beállított kiszolgáló címeRendszeres é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 nyitvaEz a QR-kód nem egy hivatkozás!Fájlra várakozássimplexmq: v%s (%2s)
@@ -1193,10 +1193,9 @@
MentésVáltásKapott hivatkozás beillesztése az ismerősökhöz történő kapcsolódáshoz…
- Kód beolvasása
+ BeolvasásPort megnyitása a tűzfalonindítás…
- Szín mentéseLeállításelküldveSOCKS proxy használata
@@ -1206,7 +1205,7 @@
Alkalmazás képernyőjének védelmeQR-kód megjelenítésevideóhívás
- Nem kedvenc
+ Kedvenc törléseÜzenet kézbesítési jelentések küldéseSimpleX azonosítóKoppintson a
@@ -1230,7 +1229,7 @@
Jelmondat mentése és csevegés megnyitásaBeá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ó kikapcsolvahét
@@ -1278,7 +1277,7 @@
Sikertelen kiszolgáló-teszt!Kapcsolat ellenőrzéseTudjon 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ésekapcsolattartási azonosító-hivatkozáson keresztülSimpleX 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
@@ -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 tiltvaKapcsolat izolációs módAkkor 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épenAz 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áraInkognitó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 csoportbanA 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élMunkamenet kódKö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írjaA KÉZBESÍTÉSI JELENTÉSEKET A KÖVETKEZŐ CÍMRE KELL KÜLDENIA következő üzenet azonosítója hibás (kisebb vagy egyenlő az előzővel).
@@ -1619,7 +1618,7 @@
Letiltva az admin által%s letiltvaMindenki számára letiltva
- Tag letiltása mindenki számára?
+ Mindenki számára letiltja ezt a tagot?%d üzenet letiltva az admin általLetiltás feloldása mindenki számáraMindenki számára feloldja a tag letiltását?
@@ -1733,7 +1732,7 @@
minden tagSimpleX hivatkozásA 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 tiltvaFájlok és média tartalom küldése le van tiltvaA SimpleX hivatkozások küldése engedélyezve van.
@@ -1764,4 +1763,73 @@
ProfilképekProfilkép alakzatNé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
\ 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..eb72c9b851 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, vietatoRipristina i colori
- Salva coloreImposta 1 giornoImposta le preferenze del gruppoTema
@@ -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\'INTERFACCIAI 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 @@
SecondarioMessaggio ricevutoMessaggio inviato
- Titolo
+ TitoliLink una tantumInfo sull\'indirizzo SimpleXTutti i tuoi contatti resteranno connessi. L\'aggiornamento del profilo verrà inviato ai tuoi contatti.
@@ -1769,4 +1768,73 @@
La fonte del messaggio resta privata.Forma delle immagini del profiloQuadrata, 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.
+ Sì
+ 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
\ 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..ceb8ab1664 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,14 @@
プロフィール画像正方形、円形またはその中間プロフィール画像をシェイプ
+ 宛先サーバエラー: %1$s
+ エラー: %1$s
+ プライベートルーティング
+ 不明なリレー
+ 未保護
+ ダウングレードを許可
+ 常時
+ 常時プライベートルーティングを使用
+ プライベートメッセージルーティング
+ メッセージステータスを表示
\ 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 neberodytiTamsusAtstatyti spalvas
- Įrašyti spalvąGrupės parinktysIštrinti visiemsTiesioginė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..d741001717 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.ThemaKleuren resetten
- Kleur opslaanSysteemjagekregen, verboden
@@ -1767,4 +1766,44 @@
Vorm profiel afbeeldingenProfiel afbeeldingenVierkant, 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).
\ 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..ea968cfb67 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, zabronioneResetuj kolory
- Zapisz kolorUstaw 1 dzieńSystemSystem
@@ -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 INTERFEJSUTwoje 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,73 @@
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
\ 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 @@
ReverterSalvarRedefinir cores
- Salvar corinterface italianaNotificações periódicasCâ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 principalConexãoO 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 corvocê partilhou ligação de utilização únicavocê partilhou ligação anónima de utilização únicaLigaçã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..cb6187ee85 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,20 @@
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?
\ 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..33b511c1e5 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 @@
ТемнаяТема
- Сохранить цветСбросить цветаАкцент
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..dc9f488572 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ı kaydetAyarlar kaydedilsin mi?Kaydet ve konuştuğun kişilere bildir
@@ -122,7 +122,6 @@
SimpleXKoyu temaTema
- Rengi kaydetTemayı içe aktarTemayı içe aktarırken hata oluştuDosyanı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.
@@ -1770,4 +1769,73 @@
Profil resimleriProfil resimlerini şekillendirKare,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 röleler
+ 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,özel 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 aktarıcıları 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).
\ 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..8c2298afef 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,81 @@
Використовуйте додаток під час розмови.Підтвердіть парольну фразуВи можете спробувати ще раз.
+ Перевищено ліміт - одержувач не отримав раніше надіслані повідомлення.
+ Помилка сервера призначення: %1$s
+ Помилка: %1$s
+ Попередження про доставку повідомлення
+ Проблеми з мережею - термін дії повідомлення закінчився після багатьох спроб надіслати його.
+ Сервер переадресації: %1$s
+\nПомилка сервера призначення: %2$s
+ Сервер переадресації: %1$s
+\nПомилка: %2$s
+ Мікрофон
+ Джерело повідомлення залишається приватним.
+ Завжди
+ Завжди використовуйте приватну маршрутизацію.
+ Режим маршрутизації повідомлень
+ НЕ використовуйте приватну маршрутизацію.
+ Ні
+ НЕ надсилайте повідомлення напряму, навіть якщо ваш сервер або сервер призначення не підтримує приватну маршрутизацію.
+ Камера
+ Надати в налаштуваннях
+ Навушники
+ Стільниковий
+ власники
+ Більш надійне з\'єднання з мережею.
+ Керування мережею
+ Пересилання та збереження повідомлень
+ Переадресувати повідомлення…
+ Дозволити зниження рейтингу
+ Тема програми
+ Темний режим
+ Завантажити
+ Навушник
+ Увімкнено для
+ Повідомлення про помилку, зв\'яжіться з розробниками.
+ Файли та медіафайли заборонені
+ Знайдіть цей дозвіл у налаштуваннях Android і надайте його вручну.
+ Переслати
+ Переслано
+ Переслано з
+ Учасники групи можуть надсилати посилання SimpleX.
+ Звуки вхідного дзвінка
+ Світлий режим
+ Запасний варіант маршрутизації повідомлень
+ МАРШРУТИЗАЦІЯ ПРИВАТНИХ ПОВІДОМЛЕНЬ
+ переслано
+ Інше
+ Дозволити надсилати посилання SimpleX.
+ Заборонити надсилання посилань SimpleX
+ Немає підключення до мережі
+ Ніколи
+ Приватна маршрутизація
+ Bluetooth
+ Камера та мікрофон
+ Надайте дозвіл(и) на здійснення дзвінків
+ Відкрити налаштування
+ ФАЙЛИ
+ Зображення профілю
+ Підключення до мережі
+ адміністратори
+ всі учасники
+ Литовський інтерфейс
+ Надавати дозволи
+ Кольори чату
+ Тема чату
+ Тема профілю
+ Темна
+ Додатковий акцент 2
+ Розширені налаштування
+ Усі кольорові режими
+ Застосувати до
+ Колірний режим
+ Темна
+ Кольори темного режиму
+ Заповнити
+ Підходить
+ Доброго дня!
+ Доброго ранку!
+ Світлий
\ 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..362d6a30f0 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,73 @@
个人资料图改变个人资料图形状方形、圆形、或两者之间的任意形状
+ 超出了额度 — 收信人没收到之前发送的消息。
+ 目标服务器错误:%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
+ 高级设置
+ 聊天颜色
+ 重复
+ 聊天主题
+ 重置颜色
+ 缩放
\ 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..36149c8248 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
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..9925a6346b 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
@@ -18,6 +18,7 @@ import java.io.File
fun main() {
initHaskell()
+ runMigrations()
initApp()
tmpDir.deleteRecursively()
tmpDir.mkdir()
diff --git a/apps/multiplatform/gradle.properties b/apps/multiplatform/gradle.properties
index 01e3b17296..c90510566a 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.2
-android.version_code=210
+android.version_name=5.8-beta.3
+android.version_code=214
-desktop.version_name=5.8-beta.2
-desktop.version_code=47
+desktop.version_name=5.8-beta.3
+desktop.version_code=49
kotlin.version=1.9.23
gradle.plugin.version=8.2.0
diff --git a/cabal.project b/cabal.project
index a68df42bf7..e04a03b210 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: bd67844169d2206d8543c01e6ed966315115b0e3
+ tag: 0f663bd569f5d282fc13cd969391f30d873d73a0
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 39410d16e0..accab5f0c4 100644
--- a/package.yaml
+++ b/package.yaml
@@ -1,5 +1,5 @@
name: simplex-chat
-version: 5.8.0.3
+version: 5.8.0.4
#synopsis:
#description:
homepage: https://github.com/simplex-chat/simplex-chat#readme
diff --git a/scripts/nix/sha256map.nix b/scripts/nix/sha256map.nix
index 468ca71307..a719aa9629 100644
--- a/scripts/nix/sha256map.nix
+++ b/scripts/nix/sha256map.nix
@@ -1,5 +1,5 @@
{
- "https://github.com/simplex-chat/simplexmq.git"."bd67844169d2206d8543c01e6ed966315115b0e3" = "1g218q15hrg21h8gyidavfys5zx8dzmxq7iwfm5bfaw71grpd7pn";
+ "https://github.com/simplex-chat/simplexmq.git"."0f663bd569f5d282fc13cd969391f30d873d73a0" = "0xv3fjq6acr25hkkpb4grmdj0hls9z5qnq0dx7h7lxck60q0v9w8";
"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 11c0cc0731..b70c2dd71b 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.3
+version: 5.8.0.4
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
diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs
index e259629d2b..07f8586f33 100644
--- a/src/Simplex/Chat.hs
+++ b/src/Simplex/Chat.hs
@@ -1472,14 +1472,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})
@@ -1550,8 +1550,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
@@ -1806,12 +1806,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
@@ -2338,8 +2346,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 =
@@ -2351,8 +2359,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
@@ -2365,12 +2373,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} ->
@@ -3945,14 +3960,19 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
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)
@@ -4083,6 +4103,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
@@ -4123,12 +4144,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)
@@ -4477,9 +4501,11 @@ processAgentMessageConn vr user@User {userId} corrId agentConnId agentMessage =
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
@@ -4515,13 +4541,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
@@ -4651,7 +4681,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 ()
@@ -4703,7 +4733,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 ()
@@ -4743,17 +4773,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 ()
@@ -6544,16 +6597,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
@@ -6711,6 +6768,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
@@ -6741,21 +6799,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)
diff --git a/src/Simplex/Chat/Controller.hs b/src/Simplex/Chat/Controller.hs
index 2c4c09c79c..71c198c47c 100644
--- a/src/Simplex/Chat/Controller.hs
+++ b/src/Simplex/Chat/Controller.hs
@@ -752,6 +752,7 @@ data ChatResponse
| CRAgentMsgCounts {msgCounts :: [(Text, (Int, Int))]}
| 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}
@@ -1117,7 +1118,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 9742439fb3..6c0e93e017 100644
--- a/src/Simplex/Chat/Messages.hs
+++ b/src/Simplex/Chat/Messages.hs
@@ -945,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
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/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 0487b80c17..84b2536380 100644
--- a/src/Simplex/Chat/Store/Messages.hs
+++ b/src/Simplex/Chat/Store/Messages.hs
@@ -285,22 +285,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 =
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/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/View.hs b/src/Simplex/Chat/View.hs
index 08980f21d2..01519a7fcc 100644
--- a/src/Simplex/Chat/View.hs
+++ b/src/Simplex/Chat/View.hs
@@ -378,6 +378,7 @@ responseToView hu@(currentRH, user_) ChatConfig {logLevel, showReactions, showRe
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,9 +389,9 @@ 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)]
CRTimedAction _ _ -> []
@@ -1894,8 +1895,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 +1954,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]
@@ -2034,14 +2034,14 @@ viewChatError logLevel testView = \case
<> "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"
]
- BROKER _ NETWORK -> []
- BROKER _ TIMEOUT -> []
- AGENT A_DUPLICATE -> [withConnEntity <> "error: AGENT A_DUPLICATE" | logLevel == CLLDebug]
- AGENT (A_PROHIBITED e) -> [withConnEntity <> "error: AGENT A_PROHIBITED, " <> plain e | 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
@@ -2077,6 +2077,11 @@ 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"]
+
connEntityLabel :: ConnectionEntity -> StyledString
connEntityLabel = \case
RcvDirectMsgConnection _ (Just Contact {localDisplayName = c}) -> plain c
diff --git a/tests/ChatClient.hs b/tests/ChatClient.hs
index 83ac69ebe9..8f7bd1dc4e 100644
--- a/tests/ChatClient.hs
+++ b/tests/ChatClient.hs
@@ -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,
@@ -436,7 +436,10 @@ serverCfg =
}
withSmpServer :: IO () -> IO ()
-withSmpServer = serverBracket (`runSMPServerBlocking` serverCfg)
+withSmpServer = withSmpServer' smpServerCfg
+
+withSmpServer' :: ServerConfig -> IO () -> IO ()
+withSmpServer' cfg = serverBracket (`runSMPServerBlocking` cfg)
xftpTestPort :: ServiceName
xftpTestPort = "7002"
diff --git a/tests/ChatTests/Groups.hs b/tests/ChatTests/Groups.hs
index 8cd853ade0..009a26cf0b 100644
--- a/tests/ChatTests/Groups.hs
+++ b/tests/ChatTests/Groups.hs
@@ -1,6 +1,9 @@
+{-# LANGUAGE NumericUnderscores #-}
{-# LANGUAGE OverloadedStrings #-}
{-# LANGUAGE PatternSynonyms #-}
{-# LANGUAGE PostfixOperators #-}
+{-# LANGUAGE ScopedTypeVariables #-}
+{-# LANGUAGE TypeApplications #-}
module ChatTests.Groups where
@@ -13,11 +16,16 @@ import qualified Data.ByteString.Char8 as B
import Data.List (isInfixOf)
import qualified Data.Text as T
import Simplex.Chat.Controller (ChatConfig (..))
+import Simplex.Chat.Options
import Simplex.Chat.Protocol (supportedChatVRange)
import Simplex.Chat.Store (agentStoreFile, chatStoreFile)
import Simplex.Chat.Types (VersionRangeChat)
import Simplex.Chat.Types.Shared (GroupMemberRole (..))
+import Simplex.Messaging.Agent.Env.SQLite
+import Simplex.Messaging.Agent.RetryInterval
import qualified Simplex.Messaging.Agent.Store.SQLite.DB as DB
+import Simplex.Messaging.Server.Env.STM hiding (subscriptions)
+import Simplex.Messaging.Transport
import System.Directory (copyFile)
import System.FilePath ((>))
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
@@ -6072,3 +6082,73 @@ testBlockForAllCantRepeat =
[alice, cath] *<# "#team bob> 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/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/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/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 存储库",