From ab64012b08fb3b5dfefcc50563f8a3f99c8408cf Mon Sep 17 00:00:00 2001 From: jubb Date: Tue, 4 May 2021 11:40:21 +1000 Subject: [PATCH 01/35] feat: remove requestLegacyExternalStorage --- app/src/main/AndroidManifest.xml | 1 - 1 file changed, 1 deletion(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 2d8473b849..3412b6b0cb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -69,7 +69,6 @@ android:label="@string/app_name" android:largeHeap="true" android:networkSecurityConfig="@xml/network_security_configuration" - android:requestLegacyExternalStorage="true" android:supportsRtl="true" android:theme="@style/Theme.Session.DayNight" tools:replace="android:allowBackup"> From ad51bbb8477316e7b6117c440feb7cc811b8c79b Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Tue, 25 May 2021 16:24:37 +1000 Subject: [PATCH 02/35] Update open group guidelines --- .../activities/OpenGroupGuidelinesActivity.kt | 40 +++++++++++++------ 1 file changed, 28 insertions(+), 12 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/OpenGroupGuidelinesActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/OpenGroupGuidelinesActivity.kt index 2a9ebe5491..316f07ea39 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/OpenGroupGuidelinesActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/OpenGroupGuidelinesActivity.kt @@ -12,25 +12,41 @@ class OpenGroupGuidelinesActivity : BaseActionBarActivity() { super.onCreate(savedInstanceState) setContentView(R.layout.activity_open_group_guidelines) communityGuidelinesTextView.text = """ + Welcome to Oxen. + + Oxen believes privacy is an important part of our future. People have been safeguarding the right to privacy since the dawn of humanity, but the digital world has turned privacy into a privilege. Enough is enough. We're taking it back. For you. For us. For everyone. + + Oxen is a private technology stack including the Oxen blockchain, Oxen service nodes (Session’s servers), the Oxen cryptocurrency, an onion-router called Lokinet, and Session itself. + + Oxen is what makes Session possible — Session is running on the Oxen network right now. + + Find out more at https://oxen.io, or ask here! + + — + In order for our open group to be a fun environment, full of robust and constructive discussion, please follow these four simple rules: - - 1. Keep conversations on-topic and add value to the discussion (no referral links, spamming, or off-topic discussion). - - 2. You don't have to love everyone, but be civil (no baiting, excessively partisan arguments, threats, and so on; use common sense). - + + 1. Keep conversations on-topic and add value to the discussion. + + (No referral links, spamming, or off-topic discussion) + + 2. You don't have to love everyone, but be civil. + + (No baiting, excessively partisan arguments, threats, and so on. Use common sense.) + 3. Do not be a shill. Comparison and criticism is reasonable, but blatant shilling is not. - + 4. Don't post explicit content, be it excessive offensive language, or content which is sexual or violent in nature. - + If you break these rules, you’ll be warned by an admin. If your behaviour doesn’t improve, you will be removed from the open group. - + If you see or experience any destructive behaviour, please contact an admin. - + —————————— - + SCAMMER WARNING - - Trust only those with an admin tag in the chat. No admin will ever DM you first. No admin will ever message you for Oxen coins. + + Trust only those with an admin crown in chat. No admin will ever DM you first. No admin will ever message you for Oxen coins. """.trimIndent() } // endregion From efa36d4ceab0fdb1de927e714cc5e17269f90ca4 Mon Sep 17 00:00:00 2001 From: jubb Date: Wed, 26 May 2021 15:22:19 +1000 Subject: [PATCH 03/35] refactor: trim thread is now queued after batch processing messages, for other conversations still after every persisted message migrate TrimThreadJob.kt to new job system deleting more open group references in removal open group last message / last deletion now sets after processing vs after fetching --- .../securesms/database/MmsDatabase.java | 3 -- .../securesms/database/SmsDatabase.java | 3 -- .../securesms/database/Storage.kt | 11 +++++ .../securesms/database/ThreadDatabase.java | 1 + .../securesms/loki/activities/HomeActivity.kt | 3 -- .../securesms/loki/api/OpenGroupManager.kt | 8 ++-- .../loki/database/LokiMessageDatabase.kt | 28 +++++++++++ .../loki/database/LokiThreadDatabase.kt | 8 ++++ .../libsession/database/StorageProtocol.kt | 1 + .../libsession/messaging/jobs/JobQueue.kt | 8 ++-- .../jobs/SessionJobManagerFactories.kt | 3 +- .../messaging/jobs/TrimThreadJob.kt | 47 +++++++++++++++++++ .../messaging/open_groups/OpenGroupAPIV2.kt | 12 ----- .../pollers/OpenGroupPollerV2.kt | 28 +++++++++-- 14 files changed, 130 insertions(+), 34 deletions(-) create mode 100644 libsession/src/main/java/org/session/libsession/messaging/jobs/TrimThreadJob.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java index 4ee953b0b6..75dca96013 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.java @@ -46,7 +46,6 @@ import org.thoughtcrime.securesms.database.model.MediaMmsMessageRecord; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.NotificationMmsMessageRecord; import org.thoughtcrime.securesms.database.model.Quote; -import org.thoughtcrime.securesms.jobs.TrimThreadJob; import org.session.libsession.messaging.messages.signal.IncomingMediaMessage; import org.thoughtcrime.securesms.mms.MmsException; import org.session.libsession.messaging.messages.signal.OutgoingExpirationUpdateMessage; @@ -668,7 +667,6 @@ public class MmsDatabase extends MessagingDatabase { } notifyConversationListeners(threadId); - ApplicationContext.getInstance(context).getJobManager().add(new TrimThreadJob(threadId)); return Optional.of(new InsertResult(messageId, threadId)); } @@ -812,7 +810,6 @@ public class MmsDatabase extends MessagingDatabase { DatabaseFactory.getThreadDatabase(context).setLastSeen(threadId); DatabaseFactory.getThreadDatabase(context).setHasSent(threadId, true); - ApplicationContext.getInstance(context).getJobManager().add(new TrimThreadJob(threadId)); return messageId; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java index 613bff7414..dfbc39f156 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SmsDatabase.java @@ -35,7 +35,6 @@ import org.session.libsession.utilities.IdentityKeyMismatchList; import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper; import org.thoughtcrime.securesms.database.model.MessageRecord; import org.thoughtcrime.securesms.database.model.SmsMessageRecord; -import org.thoughtcrime.securesms.jobs.TrimThreadJob; import org.session.libsession.messaging.messages.signal.IncomingGroupMessage; import org.session.libsession.messaging.messages.signal.IncomingTextMessage; import org.session.libsession.messaging.messages.signal.OutgoingTextMessage; @@ -414,7 +413,6 @@ public class SmsDatabase extends MessagingDatabase { notifyConversationListeners(threadId); - ApplicationContext.getInstance(context).getJobManager().add(new TrimThreadJob(threadId)); return Optional.of(new InsertResult(messageId, threadId)); } @@ -484,7 +482,6 @@ public class SmsDatabase extends MessagingDatabase { notifyConversationListeners(threadId); - ApplicationContext.getInstance(context).getJobManager().add(new TrimThreadJob(threadId)); return messageId; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 5f498fa100..47e6ab99c9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.database +import android.app.job.JobScheduler import android.content.Context import android.net.Uri import org.session.libsession.database.StorageProtocol @@ -161,6 +162,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, messageID = result.messageId } } + val threadID = message.threadID + if (openGroupID.isNullOrEmpty() && threadID != null && threadID >= 0) { + JobQueue.shared.add(TrimThreadJob(threadID)) + // open group trim thread job is scheduled after processing + } return messageID } @@ -539,6 +545,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return threadDB.getLastUpdated(threadID) } + override fun trimThread(threadID: Long, threadLimit: Int) { + val threadDB = DatabaseFactory.getThreadDatabase(context) + threadDB.trimThread(threadID, threadLimit) + } + override fun getAttachmentDataUri(attachmentId: AttachmentId): Uri { return PartAuthority.getAttachmentDataUri(attachmentId) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java index ebb0dbab26..2ba85ca66b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ThreadDatabase.java @@ -423,6 +423,7 @@ public class ThreadDatabase extends Database { DatabaseFactory.getSmsDatabase(context).deleteThread(threadId); DatabaseFactory.getMmsDatabase(context).deleteThread(threadId); DatabaseFactory.getDraftDatabase(context).clearDrafts(threadId); + DatabaseFactory.getLokiMessageDatabase(context).deleteThread(threadId); deleteThread(threadId); notifyConversationListeners(threadId); notifyConversationListListeners(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index 0e5ccd6fde..9a9a158cfe 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -330,9 +330,6 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), // Delete the conversation val v2OpenGroup = DatabaseFactory.getLokiThreadDatabase(context).getOpenGroupChat(threadID) if (v2OpenGroup != null) { - val apiDB = DatabaseFactory.getLokiAPIDatabase(context) - apiDB.removeLastMessageServerID(v2OpenGroup.room, v2OpenGroup.server) - apiDB.removeLastDeletionServerID(v2OpenGroup.room, v2OpenGroup.server) OpenGroupManager.delete(v2OpenGroup.server, v2OpenGroup.room, this@HomeActivity) } else { ThreadUtils.queue { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/api/OpenGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/api/OpenGroupManager.kt index b20de78bdf..a1b2b208f7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/api/OpenGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/api/OpenGroupManager.kt @@ -89,7 +89,7 @@ object OpenGroupManager { val openGroup = OpenGroupV2(server, room, info.name, publicKey) threadDB.setOpenGroupChat(openGroup, threadID) // Start the poller if needed - if (pollers[server] == null) { + pollers[server]?.startIfNeeded() ?: run { val poller = OpenGroupPollerV2(server, executorService) Util.runOnMain { poller.startIfNeeded() } pollers[server] = poller @@ -111,9 +111,11 @@ object OpenGroupManager { pollers.remove(server) } // Delete + storage.removeLastDeletionServerID(room, server) + storage.removeLastMessageServerID(room, server) + val lokiThreadDB = DatabaseFactory.getLokiThreadDatabase(context) + lokiThreadDB.removeOpenGroupChat(threadID) ThreadUtils.queue { - storage.removeLastDeletionServerID(room, server) - storage.removeLastMessageServerID(room, server) GroupManager.deleteGroup(groupID, context) // Must be invoked on a background thread } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt index 83e64f884c..137579f07e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt @@ -8,6 +8,7 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.loki.utilities.* import org.session.libsignal.database.LokiMessageDatabaseProtocol +import org.session.libsignal.utilities.Log class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper), LokiMessageDatabaseProtocol { @@ -131,4 +132,31 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab contentValues.put(Companion.errorMessage, errorMessage) database.insertOrUpdate(errorMessageTable, contentValues, "${Companion.messageID} = ?", arrayOf(messageID.toString())) } + + fun deleteThread(threadId: Long) { + val database = databaseHelper.writableDatabase + try { + val messages = mutableSetOf>() + database.get(messageThreadMappingTable, "${Companion.threadID} = ?", arrayOf(threadId.toString())) { cursor -> + // for each add + while (cursor.moveToNext()) { + messages.add(cursor.getLong(Companion.messageID) to cursor.getLong(Companion.serverID)) + } + } + Log.d("Test", "Need to delete ${messages.size} number of messages") + + var deletedCount = 0L + + database.beginTransaction() + messages.forEach { (messageId, serverId) -> + deletedCount += database.delete(messageIDTable, "${Companion.messageID} = ? AND ${Companion.serverID} = ?", arrayOf(messageId.toString(), serverId.toString())) + } + Log.d("Test", "Deleted $deletedCount from messageIDTable") + val mappingDeleted = database.delete(messageThreadMappingTable, "${Companion.threadID} = ?", arrayOf(threadId.toString())) + Log.d("Test", "Deleted $mappingDeleted from mapping table") + database.setTransactionSuccessful() + } finally { + database.endTransaction() + } + } } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt index b0e1cfe992..f0f6123b2e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiThreadDatabase.kt @@ -73,4 +73,12 @@ class LokiThreadDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa contentValues.put(publicChat, JsonUtil.toJson(openGroupV2.toJson())) database.insertOrUpdate(publicChatTable, contentValues, "${Companion.threadID} = ?", arrayOf(threadID.toString())) } + + fun removeOpenGroupChat(threadID: Long) { + if (threadID < 0) return + + val database = databaseHelper.writableDatabase + database.delete(publicChatTable,"${Companion.threadID} = ?", arrayOf(threadID.toString())) + } + } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index 00b856d809..ea5931f3b9 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -130,6 +130,7 @@ interface StorageProtocol { fun getThreadId(recipient: Recipient): Long? fun getThreadIdForMms(mmsId: Long): Long fun getLastUpdated(threadID: Long): Long + fun trimThread(threadID: Long, threadLimit: Int) // Contacts fun getContactWithSessionID(sessionID: String): Contact? diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt index 147a9f1078..e6803e89dc 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/JobQueue.kt @@ -37,9 +37,9 @@ class JobQueue : JobDelegate { init { // Process jobs scope.launch { - val rxQueue = Channel(capacity = 1024) - val txQueue = Channel(capacity = 1024) - val attachmentQueue = Channel(capacity = 1024) + val rxQueue = Channel(capacity = 4096) + val txQueue = Channel(capacity = 4096) + val attachmentQueue = Channel(capacity = 4096) val receiveJob = processWithDispatcher(rxQueue, rxDispatcher) val txJob = processWithDispatcher(txQueue, txDispatcher) @@ -50,7 +50,7 @@ class JobQueue : JobDelegate { when (job) { is NotifyPNServerJob, is AttachmentUploadJob, is MessageSendJob -> txQueue.send(job) is AttachmentDownloadJob -> attachmentQueue.send(job) - is MessageReceiveJob -> rxQueue.send(job) + is MessageReceiveJob, is TrimThreadJob -> rxQueue.send(job) else -> throw IllegalStateException("Unexpected job type.") } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/SessionJobManagerFactories.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/SessionJobManagerFactories.kt index c681a67f3d..15526981cf 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/SessionJobManagerFactories.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/SessionJobManagerFactories.kt @@ -10,7 +10,8 @@ class SessionJobManagerFactories { AttachmentUploadJob.KEY to AttachmentUploadJob.Factory(), MessageReceiveJob.KEY to MessageReceiveJob.Factory(), MessageSendJob.KEY to MessageSendJob.Factory(), - NotifyPNServerJob.KEY to NotifyPNServerJob.Factory() + NotifyPNServerJob.KEY to NotifyPNServerJob.Factory(), + TrimThreadJob.KEY to TrimThreadJob.Factory() ) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/TrimThreadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/TrimThreadJob.kt new file mode 100644 index 0000000000..1122e73292 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/TrimThreadJob.kt @@ -0,0 +1,47 @@ +package org.session.libsession.messaging.jobs + +import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.messaging.utilities.Data +import org.session.libsession.utilities.TextSecurePreferences + +class TrimThreadJob(val threadId: Long) : Job { + companion object { + const val KEY: String = "TrimThreadJob" + + const val THREAD_ID = "thread_id" + } + + override var delegate: JobDelegate? = null + override var id: String? = null + override var failureCount: Int = 0 + + override val maxFailureCount: Int = 1 + + override fun execute() { + val context = MessagingModuleConfiguration.shared.context + val trimmingEnabled = TextSecurePreferences.isThreadLengthTrimmingEnabled(context) + val threadLengthLimit = TextSecurePreferences.getThreadTrimLength(context) + + if (trimmingEnabled) { + MessagingModuleConfiguration.shared.storage.trimThread(threadId, threadLengthLimit) + } + + delegate?.handleJobSucceeded(this) + } + + override fun serialize(): Data { + return Data.Builder() + .putLong(THREAD_ID, threadId) + .build() + } + + override fun getFactoryKey(): String = "TrimThreadJob" + + class Factory : Job.Factory { + + override fun create(data: Data): TrimThreadJob { + return TrimThreadJob(data.getLong(THREAD_ID)) + } + } + +} \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt index d6f9aa6c66..a1bcfe441c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt @@ -258,9 +258,6 @@ object OpenGroupAPIV2 { } private fun parseMessages(room: String, server: String, rawMessages: List>): List { - val storage = MessagingModuleConfiguration.shared.storage - val lastMessageServerID = storage.getLastMessageServerID(room, server) ?: 0 - var currentLastMessageServerID = lastMessageServerID val messages = rawMessages.mapNotNull { json -> json as Map try { @@ -275,15 +272,11 @@ object OpenGroupAPIV2 { Log.d("Loki", "Ignoring message with invalid signature.") return@mapNotNull null } - if (message.serverID > lastMessageServerID) { - currentLastMessageServerID = message.serverID - } message } catch (e: Exception) { null } } - storage.setLastMessageServerID(room, server, currentLastMessageServerID) return messages } // endregion @@ -404,11 +397,6 @@ object OpenGroupAPIV2 { val type = TypeFactory.defaultInstance().constructCollectionType(List::class.java, MessageDeletion::class.java) val idsAsString = JsonUtil.toJson(json["deletions"]) val deletedServerIDs = JsonUtil.fromJson>(idsAsString, type) ?: throw Error.ParsingFailed - val lastDeletionServerID = storage.getLastDeletionServerID(roomID, server) ?: 0 - val serverID = deletedServerIDs.maxByOrNull { it.id } ?: MessageDeletion.empty - if (serverID.id > lastDeletionServerID) { - storage.setLastDeletionServerID(roomID, server, serverID.id) - } // Messages val rawMessages = json["messages"] as? List> ?: return@mapNotNull null val messages = parseMessages(roomID, server, rawMessages) diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt index 4b9c9a7071..57ec540da9 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt @@ -5,6 +5,7 @@ import nl.komponents.kovenant.functional.map import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.MessageReceiveJob +import org.session.libsession.messaging.jobs.TrimThreadJob import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.messaging.open_groups.OpenGroupMessageV2 import org.session.libsession.utilities.Address @@ -15,6 +16,7 @@ import org.session.libsignal.utilities.successBackground import java.util.concurrent.ScheduledExecutorService import java.util.concurrent.ScheduledFuture import java.util.concurrent.TimeUnit +import kotlin.math.max class OpenGroupPollerV2(private val server: String, private val executorService: ScheduledExecutorService?) { var hasStarted = false @@ -44,8 +46,8 @@ class OpenGroupPollerV2(private val server: String, private val executorService: return OpenGroupAPIV2.compactPoll(rooms, server).successBackground { responses -> responses.forEach { (room, response) -> val openGroupID = "$server.$room" - handleNewMessages(openGroupID, response.messages, isBackgroundPoll) - handleDeletedMessages(openGroupID, response.deletions) + handleNewMessages(room, openGroupID, response.messages, isBackgroundPoll) + handleDeletedMessages(room, openGroupID, response.deletions) if (secondToLastJob == null && !isCaughtUp) { isCaughtUp = true } @@ -55,8 +57,13 @@ class OpenGroupPollerV2(private val server: String, private val executorService: }.map { } } - private fun handleNewMessages(openGroupID: String, messages: List, isBackgroundPoll: Boolean) { - if (!hasStarted) { return } + private fun handleNewMessages(room: String, openGroupID: String, messages: List, isBackgroundPoll: Boolean) { + val storage = MessagingModuleConfiguration.shared.storage + val groupID = GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray()) + // check thread still exists + val threadId = storage.getThreadId(Address.fromSerialized(groupID)) ?: -1 + val threadExists = threadId >= 0 + if (!hasStarted || !threadExists) { return } var latestJob: MessageReceiveJob? = null messages.sortedBy { it.serverID!! }.forEach { message -> try { @@ -82,9 +89,15 @@ class OpenGroupPollerV2(private val server: String, private val executorService: Log.e("Loki", "Exception parsing message", e) } } + val currentLastMessageServerID = storage.getLastMessageServerID(room, server) ?: 0 + val actualMax = max(messages.mapNotNull { it.serverID }.maxOrNull() ?: 0, currentLastMessageServerID) + if (actualMax > 0) { + storage.setLastMessageServerID(room, server, actualMax) + } + JobQueue.shared.add(TrimThreadJob(threadId)) } - private fun handleDeletedMessages(openGroupID: String, deletedMessageServerIDs: List) { + private fun handleDeletedMessages(room: String, openGroupID: String, deletedMessageServerIDs: List) { val storage = MessagingModuleConfiguration.shared.storage val dataProvider = MessagingModuleConfiguration.shared.messageDataProvider val groupID = GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray()) @@ -99,5 +112,10 @@ class OpenGroupPollerV2(private val server: String, private val executorService: deletedMessageIDs.forEach { (messageId, isSms) -> MessagingModuleConfiguration.shared.messageDataProvider.deleteMessage(messageId, isSms) } + val currentMax = storage.getLastDeletionServerID(room, server) ?: 0L + val latestMax = deletedMessageServerIDs.maxOrNull() ?: 0L + if (latestMax > currentMax && latestMax != 0L) { + storage.setLastDeletionServerID(room, server, latestMax) + } } } \ No newline at end of file From ae23266058f78f33aefe500ea015eff116692985 Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Wed, 26 May 2021 16:34:08 +1000 Subject: [PATCH 04/35] wip: implement ons name --- .../libsession/snode/OnionRequestAPI.kt | 2 +- .../org/session/libsession/snode/SnodeAPI.kt | 58 ++++++++++++++++++- .../org/session/libsignal/utilities/Snode.kt | 3 +- 3 files changed, 60 insertions(+), 3 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt index 4f6aca389d..a188b4a5a8 100644 --- a/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/OnionRequestAPI.kt @@ -430,7 +430,7 @@ object OnionRequestAPI { /** * Sends an onion request to `snode`. Builds new paths as needed. */ - internal fun sendOnionRequest(method: Snode.Method, parameters: Map<*, *>, snode: Snode, publicKey: String): Promise, Exception> { + internal fun sendOnionRequest(method: Snode.Method, parameters: Map<*, *>, snode: Snode, publicKey: String? = null): Promise, Exception> { val payload = mapOf( "method" to method.rawValue, "params" to parameters ) return sendOnionRequest(Destination.Snode(snode), payload).recover { exception -> val httpRequestFailedException = exception as? HTTP.HTTPRequestFailedException diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 3f9115572c..429b2d0e48 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -3,6 +3,10 @@ package org.session.libsession.snode import android.os.Build +import com.goterl.lazysodium.LazySodiumAndroid +import com.goterl.lazysodium.SodiumAndroid +import com.goterl.lazysodium.interfaces.PwHash +import com.goterl.lazysodium.interfaces.SecretBox import nl.komponents.kovenant.* import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map @@ -17,10 +21,14 @@ import org.session.libsignal.utilities.prettifiedDescription import org.session.libsignal.utilities.removing05PrefixIfNeeded import org.session.libsignal.utilities.retryIfNeeded import org.session.libsignal.utilities.* +import org.session.libsignal.utilities.Base64 import org.session.libsignal.utilities.Log import java.security.SecureRandom +import java.util.* object SnodeAPI { + private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } + private val database: LokiAPIDatabaseProtocol get() = SnodeModule.shared.storage private val broadcaster: Broadcaster @@ -54,10 +62,14 @@ object SnodeAPI { internal sealed class Error(val description: String) : Exception(description) { object Generic : Error("An error occurred.") object ClockOutOfSync : Error("Your clock is out of sync with the Service Node network.") + // ONS + object DecryptionFailed : Error("Couldn't decrypt ONS name.") + object HashingFailed : Error("Couldn't compute ONS name hash.") + object ValidationFailed : Error("ONS name validation failed.") } // Internal API - internal fun invoke(method: Snode.Method, snode: Snode, publicKey: String, parameters: Map): RawResponsePromise { + internal fun invoke(method: Snode.Method, snode: Snode, publicKey: String? = null, parameters: Map): RawResponsePromise { val url = "${snode.address}:${snode.port}/storage_rpc/v1" if (useOnionRequests) { return OnionRequestAPI.sendOnionRequest(method, parameters, snode, publicKey) @@ -153,6 +165,50 @@ object SnodeAPI { } // Public API + fun getSessionIDFor(onsName: String): Promise { + val validationCount = 3 + val sessionIDByteCount = 33 + // Hash the ONS name using BLAKE2b + val name = onsName.toLowerCase(Locale.ENGLISH) + val nameHash = sodium.cryptoGenericHash(name) + val base64EncodedNameHash = nameHash + // Ask 3 different snodes for the Session ID associated with the given name hash + val parameters = mapOf( + "endpoint" to "ons_resolve", + "params" to mapOf( "type" to 0, "name_hash" to base64EncodedNameHash ) + ) + val promises = (0..validationCount).map { + getRandomSnode().bind { snode -> + invoke(Snode.Method.OxenDaemonRPCCall, snode, null, parameters) + } + } + val deferred = deferred() + val promise = deferred.promise + all(promises).success { results -> + val sessionIDs = mutableListOf() + for (json in results) { + val intermediate = json["result"] as? Map<*, *> + val hexEncodedCiphertext = intermediate?.get("encrypted_value") as? String + if (hexEncodedCiphertext != null) { + val ciphertext = Hex.fromStringCondensed(hexEncodedCiphertext) + val isArgon2Based = (intermediate["nonce"] == null) + if (isArgon2Based) { + // Handle old Argon2-based encryption used before HF16 + val salt = ByteArray(PwHash.SALTBYTES) + val key = sodium.cryptoPwHash(name, SecretBox.KEYBYTES, salt, PwHash.OPSLIMIT_MODERATE, PwHash.MEMLIMIT_MODERATE, PwHash.Alg.PWHASH_ALG_ARGON2ID13) + val nonce = ByteArray(SecretBox.NONCEBYTES) + val sessionID = sodium.cryptoSecretBoxOpenEasy(ciphertext, nonce, key) + } else { + + } + } else { + deferred.reject(Error.Generic) + } + } + } + return promise + } + fun getTargetSnodes(publicKey: String): Promise, Exception> { // SecureRandom() should be cryptographically secure return getSwarm(publicKey).map { it.shuffled(SecureRandom()).take(targetSwarmSnodeCount) } diff --git a/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt b/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt index af4a1f694f..92c30095ef 100644 --- a/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt +++ b/libsignal/src/main/java/org/session/libsignal/utilities/Snode.kt @@ -6,7 +6,8 @@ class Snode(val address: String, val port: Int, val publicKeySet: KeySet?) { public enum class Method(val rawValue: String) { GetSwarm("get_snodes_for_pubkey"), GetMessages("retrieve"), - SendMessage("store") + SendMessage("store"), + OxenDaemonRPCCall("oxend_request") } data class KeySet(val ed25519Key: String, val x25519Key: String) From e389044f255ffdf5f17a63d6f0000f769befcdc7 Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 27 May 2021 11:05:47 +1000 Subject: [PATCH 05/35] refactor: remove test logs --- .../securesms/loki/database/LokiMessageDatabase.kt | 5 ----- 1 file changed, 5 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt index 137579f07e..fc1aa4351b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/database/LokiMessageDatabase.kt @@ -143,17 +143,12 @@ class LokiMessageDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab messages.add(cursor.getLong(Companion.messageID) to cursor.getLong(Companion.serverID)) } } - Log.d("Test", "Need to delete ${messages.size} number of messages") - var deletedCount = 0L - database.beginTransaction() messages.forEach { (messageId, serverId) -> deletedCount += database.delete(messageIDTable, "${Companion.messageID} = ? AND ${Companion.serverID} = ?", arrayOf(messageId.toString(), serverId.toString())) } - Log.d("Test", "Deleted $deletedCount from messageIDTable") val mappingDeleted = database.delete(messageThreadMappingTable, "${Companion.threadID} = ?", arrayOf(threadId.toString())) - Log.d("Test", "Deleted $mappingDeleted from mapping table") database.setTransactionSuccessful() } finally { database.endTransaction() From 468b8f25c50f0c1af72525c0e6261a66a3b998a1 Mon Sep 17 00:00:00 2001 From: jubb Date: Thu, 27 May 2021 11:06:47 +1000 Subject: [PATCH 06/35] refactor: improve docs --- .../main/java/org/thoughtcrime/securesms/database/Storage.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 47e6ab99c9..765d02cf79 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -163,9 +163,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } } val threadID = message.threadID + // open group trim thread job is scheduled after processing in OpenGroupPollerV2 if (openGroupID.isNullOrEmpty() && threadID != null && threadID >= 0) { JobQueue.shared.add(TrimThreadJob(threadID)) - // open group trim thread job is scheduled after processing } return messageID } From 85120b57ea1101c1659f840aa415f265b66d9f9f Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 27 May 2021 15:00:16 +1000 Subject: [PATCH 07/35] Batch conversation updates --- .../securesms/ApplicationContext.java | 12 +++--- .../ConversationNotificationDebouncer.kt | 37 +++++++++++++++++++ .../securesms/database/Database.java | 10 ++--- .../securesms/database/Storage.kt | 20 ++++------ .../securesms/loki/activities/HomeActivity.kt | 9 +---- .../securesms/loki/views/ConversationView.kt | 9 +---- .../libsession/database/StorageProtocol.kt | 2 +- .../ReceivedMessageHandler.kt | 2 +- .../libsession/utilities/Debouncer.java | 1 - 9 files changed, 60 insertions(+), 42 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/database/ConversationNotificationDebouncer.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java index 4865b29a2c..0eacf377a5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java +++ b/app/src/main/java/org/thoughtcrime/securesms/ApplicationContext.java @@ -21,6 +21,7 @@ import android.os.AsyncTask; import android.os.Build; import android.os.Handler; import android.os.Looper; + import androidx.annotation.NonNull; import androidx.lifecycle.DefaultLifecycleObserver; import androidx.lifecycle.LifecycleOwner; @@ -47,7 +48,6 @@ import org.session.libsignal.utilities.ThreadUtils; import org.signal.aesgcmprovider.AesGcmProvider; import org.thoughtcrime.securesms.components.TypingStatusSender; import org.thoughtcrime.securesms.database.DatabaseFactory; -import org.thoughtcrime.securesms.database.RecipientDatabase; import org.thoughtcrime.securesms.dependencies.InjectableType; import org.thoughtcrime.securesms.dependencies.SignalCommunicationModule; import org.thoughtcrime.securesms.jobmanager.DependencyInjector; @@ -120,14 +120,12 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc private ProfileManager profileManager; private ObjectGraph objectGraph; private PersistentLogger persistentLogger; - - // Loki public MessageNotifier messageNotifier = null; public Poller poller = null; public Broadcaster broadcaster = null; public SignalCommunicationModule communicationModule; private Job firebaseInstanceIdJob; - private Handler threadNotificationHandler; + private Handler conversationListNotificationHandler; private volatile boolean isAppVisible; @@ -135,8 +133,8 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc return (ApplicationContext) context.getApplicationContext(); } - public Handler getThreadNotificationHandler() { - return this.threadNotificationHandler; + public Handler getConversationListNotificationHandler() { + return this.conversationListNotificationHandler; } @Override @@ -153,7 +151,7 @@ public class ApplicationContext extends MultiDexApplication implements Dependenc AppContext.INSTANCE.configureKovenant(); messageNotifier = new OptimizedMessageNotifier(new DefaultMessageNotifier()); broadcaster = new Broadcaster(this); - threadNotificationHandler = new Handler(Looper.getMainLooper()); + conversationListNotificationHandler = new Handler(Looper.getMainLooper()); LokiAPIDatabase apiDB = DatabaseFactory.getLokiAPIDatabase(this); MessagingModuleConfiguration.Companion.configure(this, DatabaseFactory.getStorage(this), diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/ConversationNotificationDebouncer.kt b/app/src/main/java/org/thoughtcrime/securesms/database/ConversationNotificationDebouncer.kt new file mode 100644 index 0000000000..89c61a2e63 --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/database/ConversationNotificationDebouncer.kt @@ -0,0 +1,37 @@ +package org.thoughtcrime.securesms.database + +import android.annotation.SuppressLint +import android.content.Context +import android.os.Handler +import android.os.Looper +import org.session.libsession.utilities.Debouncer +import org.thoughtcrime.securesms.ApplicationContext + +class ConversationNotificationDebouncer(private val context: Context) { + private val threadIDs = mutableSetOf() + private val handler = Handler(Looper.getMainLooper()) + private val debouncer = Debouncer(handler, 250); + + companion object { + @SuppressLint("StaticFieldLeak") + lateinit var shared: ConversationNotificationDebouncer + + fun get(context: Context): ConversationNotificationDebouncer { + if (::shared.isInitialized) { return shared } + shared = ConversationNotificationDebouncer(context) + return shared + } + } + + fun notify(threadID: Long) { + threadIDs.add(threadID) + debouncer.publish { publish() } + } + + private fun publish() { + for (threadID in threadIDs) { + context.contentResolver.notifyChange(DatabaseContentProviders.Conversation.getUriForThread(threadID), null) + } + threadIDs.clear() + } +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java index b1d1810a5c..3f57b1573d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Database.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Database.java @@ -35,13 +35,13 @@ public abstract class Database { protected SQLCipherOpenHelper databaseHelper; protected final Context context; - private final Debouncer threadNotificationDebouncer; + private final Debouncer conversationListNotificationDebouncer; @SuppressLint("WrongConstant") public Database(Context context, SQLCipherOpenHelper databaseHelper) { - this.context = context; + this.context = context; this.databaseHelper = databaseHelper; - this.threadNotificationDebouncer = new Debouncer(ApplicationContext.getInstance(context).getThreadNotificationHandler(), 250); + this.conversationListNotificationDebouncer = new Debouncer(ApplicationContext.getInstance(context).getConversationListNotificationHandler(), 250); } protected void notifyConversationListeners(Set threadIds) { @@ -50,11 +50,11 @@ public abstract class Database { } protected void notifyConversationListeners(long threadId) { - context.getContentResolver().notifyChange(DatabaseContentProviders.Conversation.getUriForThread(threadId), null); + ConversationNotificationDebouncer.Companion.get(context).notify(threadId); } protected void notifyConversationListListeners() { - threadNotificationDebouncer.publish(()->context.getContentResolver().notifyChange(DatabaseContentProviders.ConversationList.CONTENT_URI, null)); + conversationListNotificationDebouncer.publish(()->context.getContentResolver().notifyChange(DatabaseContentProviders.ConversationList.CONTENT_URI, null)); } protected void notifyStickerListeners() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index 5f498fa100..b60a91c6dd 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -3,8 +3,8 @@ package org.thoughtcrime.securesms.database import android.content.Context import android.net.Uri import org.session.libsession.database.StorageProtocol -import org.session.libsession.messaging.jobs.* import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.messaging.jobs.* import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.signal.* import org.session.libsession.messaging.messages.signal.IncomingTextMessage @@ -16,20 +16,15 @@ import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAt import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel -import org.session.libsession.utilities.Address -import org.session.libsession.utilities.Address.Companion.fromSerialized -import org.session.libsession.utilities.GroupRecord -import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.messaging.utilities.UpdateMessageData -import org.session.libsession.utilities.GroupUtil -import org.session.libsession.utilities.IdentityKeyUtil -import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.ProfileKeyUtil +import org.session.libsession.utilities.* +import org.session.libsession.utilities.Address.Companion.fromSerialized +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.ecc.ECKeyPair -import org.session.libsignal.utilities.KeyHelper -import org.session.libsignal.utilities.guava.Optional import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceGroup +import org.session.libsignal.utilities.KeyHelper +import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.jobs.RetrieveProfileAvatarJob @@ -100,7 +95,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, override fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List): Long? { var messageID: Long? = null val senderAddress = Address.fromSerialized(message.sender!!) - val isUserSender = message.sender!! == getUserPublicKey() + val isUserSender = (message.sender!! == getUserPublicKey()) val group: Optional = when { openGroupID != null -> Optional.of(SignalServiceGroup(openGroupID.toByteArray(), SignalServiceGroup.GroupType.PUBLIC_CHAT)) groupPublicKey != null -> { @@ -120,7 +115,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, senderAddress } val targetRecipient = Recipient.from(context, targetAddress, false) - if (message.isMediaMessage() || attachments.isNotEmpty()) { val quote: Optional = if (quotes != null) Optional.of(quotes) else Optional.absent() val linkPreviews: Optional> = if (linkPreview.isEmpty()) Optional.absent() else Optional.of(linkPreview.mapNotNull { it!! }) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt index 0e5ccd6fde..65b34a3b0d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/HomeActivity.kt @@ -51,17 +51,12 @@ import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.mms.GlideRequests import java.io.IOException -class HomeActivity : PassphraseRequiredActionBarActivity(), - ConversationClickListener, - SeedReminderViewDelegate, - NewConversationButtonSetViewDelegate { +class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickListener, SeedReminderViewDelegate, NewConversationButtonSetViewDelegate { private lateinit var glide: GlideRequests private var broadcastReceiver: BroadcastReceiver? = null private val publicKey: String - get() { - return TextSecurePreferences.getLocalNumber(this)!! - } + get() = TextSecurePreferences.getLocalNumber(this)!! // region Lifecycle override fun onCreate(savedInstanceState: Bundle?, isReady: Boolean) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt index efbaa24dbd..7ef90c0129 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ConversationView.kt @@ -35,13 +35,8 @@ class ConversationView : LinearLayout { setUpViewHierarchy() } - constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int, defStyleRes: Int) : super(context, attrs, defStyleAttr, defStyleRes) { - setUpViewHierarchy() - } - private fun setUpViewHierarchy() { - LayoutInflater.from(context) - .inflate(R.layout.view_conversation, this) + LayoutInflater.from(context).inflate(R.layout.view_conversation, this) } // endregion @@ -75,7 +70,7 @@ class ConversationView : LinearLayout { typingIndicatorView.visibility = if (isTyping) View.VISIBLE else View.GONE statusIndicatorImageView.visibility = View.VISIBLE when { - !thread.isOutgoing || thread.isVerificationStatusChange -> statusIndicatorImageView.visibility = View.GONE + !thread.isOutgoing -> statusIndicatorImageView.visibility = View.GONE thread.isFailed -> statusIndicatorImageView.setImageResource(R.drawable.ic_error) thread.isPending -> statusIndicatorImageView.setImageResource(R.drawable.ic_circle_dot_dot_dot) thread.isRemoteRead -> statusIndicatorImageView.setImageResource(R.drawable.ic_filled_circle_check) diff --git a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt index 00b856d809..890077be0a 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -12,8 +12,8 @@ import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.OpenGroupV2 import org.session.libsession.messaging.sending_receiving.attachments.AttachmentId -import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment +import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.utilities.Address diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt index aa347084e0..ea32099905 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/ReceivedMessageHandler.kt @@ -234,7 +234,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, proto: SignalS } // Cancel any typing indicators if needed cancelTypingIndicatorsIfNeeded(message.sender!!) - //Notify the user if needed + // Notify the user if needed SSKEnvironment.shared.notificationManager.updateNotification(context, threadID) } //endregion diff --git a/libsession/src/main/java/org/session/libsession/utilities/Debouncer.java b/libsession/src/main/java/org/session/libsession/utilities/Debouncer.java index 84312a4c2c..1dc8a300f3 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/Debouncer.java +++ b/libsession/src/main/java/org/session/libsession/utilities/Debouncer.java @@ -12,7 +12,6 @@ import android.os.Handler; * See http://rxmarbles.com/#debounce */ public class Debouncer { - private final Handler handler; private final long threshold; From e6cdd3ee0e936f3759f5943eaa1114c2aabab5c5 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 27 May 2021 15:31:48 +1000 Subject: [PATCH 08/35] add logic and UI in create private chat view --- .../activities/CreatePrivateChatActivity.kt | 48 ++++++++++++++++++- .../layout/activity_create_private_chat.xml | 42 ++++++++++++---- 2 files changed, 80 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt index 25c6dec9a4..40947d1543 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt @@ -1,5 +1,7 @@ package org.thoughtcrime.securesms.loki.activities +import android.animation.Animator +import android.animation.AnimatorListenerAdapter import android.content.ClipData import android.content.ClipboardManager import android.content.Context @@ -13,8 +15,14 @@ import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.Toast import kotlinx.android.synthetic.main.activity_create_private_chat.* +import kotlinx.android.synthetic.main.activity_create_private_chat.loader +import kotlinx.android.synthetic.main.activity_create_private_chat.tabLayout +import kotlinx.android.synthetic.main.activity_create_private_chat.viewPager +import kotlinx.android.synthetic.main.activity_join_public_chat.* import kotlinx.android.synthetic.main.fragment_enter_public_key.* +import kotlinx.android.synthetic.main.permissions_rationale_dialog.* import network.loki.messenger.R +import org.session.libsession.snode.SnodeAPI import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.conversation.ConversationActivity import org.session.libsession.utilities.Address @@ -48,6 +56,23 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC } // endregion + // region Updating + private fun showLoader() { + loader.visibility = View.VISIBLE + loader.animate().setDuration(150).alpha(1.0f).start() + } + + private fun hideLoader() { + loader.animate().setDuration(150).alpha(0.0f).setListener(object : AnimatorListenerAdapter() { + + override fun onAnimationEnd(animation: Animator?) { + super.onAnimationEnd(animation) + loader.visibility = View.GONE + } + }) + } + // endregion + // region Interaction override fun onOptionsItemSelected(item: MenuItem): Boolean { when(item.itemId) { @@ -60,8 +85,27 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC createPrivateChatIfPossible(hexEncodedPublicKey) } - fun createPrivateChatIfPossible(hexEncodedPublicKey: String) { - if (!PublicKeyValidation.isValid(hexEncodedPublicKey)) { return Toast.makeText(this, R.string.invalid_session_id, Toast.LENGTH_SHORT).show() } + fun createPrivateChatIfPossible(onsNameOrPublicKey: String) { + if (!PublicKeyValidation.isValid(onsNameOrPublicKey)) { + createPrivateChat(onsNameOrPublicKey) + } else { + // This could be an ONS name + showLoader() + SnodeAPI.getSessionIDFor(onsNameOrPublicKey).success { hexEncodedPublicKey -> + hideLoader() + this.createPrivateChat(hexEncodedPublicKey) + }.fail { exception -> + hideLoader() + var message = "Please check the Session ID or ONS name and try again." + exception.localizedMessage?.let { + message = it + } + Toast.makeText(this, message, Toast.LENGTH_SHORT).show() + } + } + } + + private fun createPrivateChat(hexEncodedPublicKey: String) { val recipient = Recipient.from(this, Address.fromSerialized(hexEncodedPublicKey), false) val intent = Intent(this, ConversationActivity::class.java) intent.putExtra(ConversationActivity.ADDRESS_EXTRA, recipient.address) diff --git a/app/src/main/res/layout/activity_create_private_chat.xml b/app/src/main/res/layout/activity_create_private_chat.xml index 6a1229648e..5d21096f01 100644 --- a/app/src/main/res/layout/activity_create_private_chat.xml +++ b/app/src/main/res/layout/activity_create_private_chat.xml @@ -1,14 +1,40 @@ - + android:layout_height="match_parent"> - + android:layout_height="match_parent" > - \ No newline at end of file + + + + + + + + + + + \ No newline at end of file From 49ee9b99914fe8bad9bb19c7a8a23c973acf0ff8 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 27 May 2021 15:39:46 +1000 Subject: [PATCH 09/35] Delete unnecessary transaction --- .../main/java/org/thoughtcrime/securesms/database/Storage.kt | 4 ---- 1 file changed, 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt index b60a91c6dd..26de5bccbb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -121,7 +121,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val mmsDatabase = DatabaseFactory.getMmsDatabase(context) val insertResult = if (message.sender == getUserPublicKey()) { val mediaMessage = OutgoingMediaMessage.from(message, targetRecipient, pointerAttachments, quote.orNull(), linkPreviews.orNull()?.firstOrNull()) - mmsDatabase.beginTransaction() mmsDatabase.insertSecureDecryptedMessageOutbox(mediaMessage, message.threadID ?: -1, message.sentTimestamp!!) } else { // It seems like we have replaced SignalServiceAttachment with SessionServiceAttachment @@ -129,14 +128,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, it.toSignalPointer() } val mediaMessage = IncomingMediaMessage.from(message, senderAddress, targetRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews) - mmsDatabase.beginTransaction() mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.receivedTimestamp ?: 0) } if (insertResult.isPresent) { - mmsDatabase.setTransactionSuccessful() messageID = insertResult.get().messageId } - mmsDatabase.endTransaction() } else { val smsDatabase = DatabaseFactory.getSmsDatabase(context) val isOpenGroupInvitation = (message.openGroupInvitation != null) From 5d262a4bbae89f2b1a16e0a2cf93e85254174220 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 27 May 2021 15:43:44 +1000 Subject: [PATCH 10/35] Update version number --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 60bdb4e2bf..78e8262c9d 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -143,8 +143,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 174 -def canonicalVersionName = "1.10.8" +def canonicalVersionCode = 175 +def canonicalVersionName = "1.10.9" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From 2441d86b32cbd42d02084178e74b5e8087455481 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 27 May 2021 16:07:54 +1000 Subject: [PATCH 11/35] Make member ordering consistent --- .../libsession/messaging/jobs/TrimThreadJob.kt | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/TrimThreadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/TrimThreadJob.kt index 1122e73292..b113e02543 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/jobs/TrimThreadJob.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/TrimThreadJob.kt @@ -5,34 +5,31 @@ import org.session.libsession.messaging.utilities.Data import org.session.libsession.utilities.TextSecurePreferences class TrimThreadJob(val threadId: Long) : Job { - companion object { - const val KEY: String = "TrimThreadJob" - - const val THREAD_ID = "thread_id" - } - override var delegate: JobDelegate? = null override var id: String? = null override var failureCount: Int = 0 override val maxFailureCount: Int = 1 + companion object { + const val KEY: String = "TrimThreadJob" + const val THREAD_ID = "thread_id" + } + override fun execute() { val context = MessagingModuleConfiguration.shared.context val trimmingEnabled = TextSecurePreferences.isThreadLengthTrimmingEnabled(context) val threadLengthLimit = TextSecurePreferences.getThreadTrimLength(context) - if (trimmingEnabled) { MessagingModuleConfiguration.shared.storage.trimThread(threadId, threadLengthLimit) } - delegate?.handleJobSucceeded(this) } override fun serialize(): Data { return Data.Builder() - .putLong(THREAD_ID, threadId) - .build() + .putLong(THREAD_ID, threadId) + .build() } override fun getFactoryKey(): String = "TrimThreadJob" From e71760d08f08a74afacdcc2f7562515947c76cf5 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 27 May 2021 16:08:13 +1000 Subject: [PATCH 12/35] Update build number --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 78e8262c9d..34a548ddb9 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -143,7 +143,7 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 175 +def canonicalVersionCode = 176 def canonicalVersionName = "1.10.9" def postFixSize = 10 From 193454beec2a8f2d4e6089a334ce0f3037c04f55 Mon Sep 17 00:00:00 2001 From: Niels Andriesse Date: Thu, 27 May 2021 16:19:20 +1000 Subject: [PATCH 13/35] Update build number --- app/build.gradle | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/build.gradle b/app/build.gradle index 34a548ddb9..02dd1bb594 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -143,7 +143,7 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 176 +def canonicalVersionCode = 177 def canonicalVersionName = "1.10.9" def postFixSize = 10 From 592825dcc65d118dc5b1c4d2532760d7728df487 Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 27 May 2021 16:23:15 +1000 Subject: [PATCH 14/35] implement get session id from ons name api --- .../org/session/libsession/snode/SnodeAPI.kt | 73 ++++++++++++++----- 1 file changed, 56 insertions(+), 17 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 429b2d0e48..5856633cb7 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -5,26 +5,24 @@ package org.session.libsession.snode import android.os.Build import com.goterl.lazysodium.LazySodiumAndroid import com.goterl.lazysodium.SodiumAndroid +import com.goterl.lazysodium.exceptions.SodiumException +import com.goterl.lazysodium.interfaces.AEAD +import com.goterl.lazysodium.interfaces.GenericHash import com.goterl.lazysodium.interfaces.PwHash import com.goterl.lazysodium.interfaces.SecretBox +import com.goterl.lazysodium.utils.Key import nl.komponents.kovenant.* import nl.komponents.kovenant.functional.bind import nl.komponents.kovenant.functional.map import org.session.libsession.messaging.utilities.MessageWrapper import org.session.libsignal.crypto.getRandomElement -import org.session.libsignal.protos.SignalServiceProtos -import org.session.libsignal.utilities.Snode -import org.session.libsignal.utilities.HTTP import org.session.libsignal.database.LokiAPIDatabaseProtocol -import org.session.libsignal.utilities.Broadcaster -import org.session.libsignal.utilities.prettifiedDescription -import org.session.libsignal.utilities.removing05PrefixIfNeeded -import org.session.libsignal.utilities.retryIfNeeded +import org.session.libsignal.protos.SignalServiceProtos import org.session.libsignal.utilities.* import org.session.libsignal.utilities.Base64 -import org.session.libsignal.utilities.Log import java.security.SecureRandom import java.util.* +import javax.crypto.AEADBadTagException object SnodeAPI { private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } @@ -166,12 +164,19 @@ object SnodeAPI { // Public API fun getSessionIDFor(onsName: String): Promise { + val deferred = deferred() + val promise = deferred.promise val validationCount = 3 val sessionIDByteCount = 33 // Hash the ONS name using BLAKE2b - val name = onsName.toLowerCase(Locale.ENGLISH) - val nameHash = sodium.cryptoGenericHash(name) - val base64EncodedNameHash = nameHash + val onsName = onsName.toLowerCase(Locale.ENGLISH) + val nameAsData = onsName.toByteArray() + val nameHash = ByteArray(GenericHash.BYTES) + if (!sodium.cryptoGenericHash(nameHash, nameHash.size, nameAsData, nameAsData.size.toLong())) { + deferred.reject(Error.HashingFailed) + return promise + } + val base64EncodedNameHash = Base64.encodeBytes(nameHash) // Ask 3 different snodes for the Session ID associated with the given name hash val parameters = mapOf( "endpoint" to "ons_resolve", @@ -182,29 +187,63 @@ object SnodeAPI { invoke(Snode.Method.OxenDaemonRPCCall, snode, null, parameters) } } - val deferred = deferred() - val promise = deferred.promise all(promises).success { results -> val sessionIDs = mutableListOf() for (json in results) { val intermediate = json["result"] as? Map<*, *> val hexEncodedCiphertext = intermediate?.get("encrypted_value") as? String if (hexEncodedCiphertext != null) { - val ciphertext = Hex.fromStringCondensed(hexEncodedCiphertext) val isArgon2Based = (intermediate["nonce"] == null) if (isArgon2Based) { // Handle old Argon2-based encryption used before HF16 val salt = ByteArray(PwHash.SALTBYTES) - val key = sodium.cryptoPwHash(name, SecretBox.KEYBYTES, salt, PwHash.OPSLIMIT_MODERATE, PwHash.MEMLIMIT_MODERATE, PwHash.Alg.PWHASH_ALG_ARGON2ID13) + val key: String val nonce = ByteArray(SecretBox.NONCEBYTES) - val sessionID = sodium.cryptoSecretBoxOpenEasy(ciphertext, nonce, key) + val sessionID: String + try { + key = sodium.cryptoPwHash(onsName, SecretBox.KEYBYTES, salt, PwHash.OPSLIMIT_MODERATE, PwHash.MEMLIMIT_MODERATE, PwHash.Alg.PWHASH_ALG_ARGON2ID13) + } catch (e: SodiumException) { + deferred.reject(Error.HashingFailed) + return@success + } + try { + sessionID = sodium.cryptoSecretBoxOpenEasy(hexEncodedCiphertext, nonce, Key.fromHexString(key)) + } catch (e: SodiumException) { + deferred.reject(Error.DecryptionFailed) + return@success + } + sessionIDs.add(sessionID) } else { - + val hexEncodedNonce = intermediate["nonce"] as? String + if (hexEncodedNonce == null) { + deferred.reject(Error.Generic) + return@success + } + val nonce = Hex.fromStringCondensed(hexEncodedNonce) + val key = ByteArray(GenericHash.BYTES) + if (!sodium.cryptoGenericHash(key, key.size, nameAsData, nameAsData.size.toLong(), nameHash, nameHash.size)) { + deferred.reject(Error.HashingFailed) + return@success + } + val sessionID: String + try { + sessionID = sodium.decrypt(hexEncodedCiphertext, null, nonce, Key.fromBytes(key), AEAD.Method.CHACHA20_POLY1305_IETF) + } catch (e: Exception) { + deferred.reject(Error.DecryptionFailed) + return@success + } + sessionIDs.add(sessionID) } } else { deferred.reject(Error.Generic) + return@success } } + if (sessionIDs.size == validationCount && sessionIDs.toSet().size == 1) { + deferred.resolve(sessionIDs.first()) + } else { + deferred.reject(Error.ValidationFailed) + } } return promise } From 93dfbcaae2e29f4fc09f72206c84968c1e0d1a8b Mon Sep 17 00:00:00 2001 From: Ryan ZHAO Date: Thu, 27 May 2021 16:51:59 +1000 Subject: [PATCH 15/35] clean --- .../securesms/loki/activities/CreatePrivateChatActivity.kt | 3 --- .../securesms/loki/activities/LinkDeviceActivity.kt | 3 --- app/src/main/res/layout/activity_create_private_chat.xml | 1 - app/src/main/res/values/strings.xml | 2 +- 4 files changed, 1 insertion(+), 8 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt index 40947d1543..eae640763b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt @@ -14,13 +14,10 @@ import android.view.* import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.Toast -import kotlinx.android.synthetic.main.activity_create_private_chat.* import kotlinx.android.synthetic.main.activity_create_private_chat.loader import kotlinx.android.synthetic.main.activity_create_private_chat.tabLayout import kotlinx.android.synthetic.main.activity_create_private_chat.viewPager -import kotlinx.android.synthetic.main.activity_join_public_chat.* import kotlinx.android.synthetic.main.fragment_enter_public_key.* -import kotlinx.android.synthetic.main.permissions_rationale_dialog.* import network.loki.messenger.R import org.session.libsession.snode.SnodeAPI import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt index 7e4210246b..20f8fcf795 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/LinkDeviceActivity.kt @@ -13,9 +13,6 @@ import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentPagerAdapter import androidx.lifecycle.lifecycleScope import com.google.android.material.snackbar.Snackbar -import kotlinx.android.synthetic.main.activity_create_private_chat.* -import kotlinx.android.synthetic.main.activity_create_private_chat.tabLayout -import kotlinx.android.synthetic.main.activity_create_private_chat.viewPager import kotlinx.android.synthetic.main.activity_link_device.* import kotlinx.android.synthetic.main.conversation_activity.* import kotlinx.android.synthetic.main.fragment_recovery_phrase.* diff --git a/app/src/main/res/layout/activity_create_private_chat.xml b/app/src/main/res/layout/activity_create_private_chat.xml index 5d21096f01..d437a230fc 100644 --- a/app/src/main/res/layout/activity_create_private_chat.xml +++ b/app/src/main/res/layout/activity_create_private_chat.xml @@ -6,7 +6,6 @@ android:layout_height="match_parent"> diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 8db4cfaf30..80eb6fd035 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -719,7 +719,7 @@ Scan QR Code Scan a user\'s QR code to start a session. QR codes can be found by tapping the QR code icon in account settings. - Enter Session ID of recipient + Enter Session ID or ONS name Users can share their Session ID by going into their account settings and tapping "Share Session ID", or by sharing their QR code. Session needs camera access to scan QR codes From 4a8ce784b84c047791bbe76d1e0474bd88e844da Mon Sep 17 00:00:00 2001 From: Harris Date: Fri, 28 May 2021 10:50:07 +1000 Subject: [PATCH 16/35] fix: no longer using the wrong deletion IDs for open group deletions --- .../messaging/open_groups/OpenGroupAPIV2.kt | 13 ++++--------- .../sending_receiving/pollers/OpenGroupPollerV2.kt | 10 +++++----- 2 files changed, 9 insertions(+), 14 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt index a1bcfe441c..ed8c9fa05a 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt @@ -17,14 +17,9 @@ import org.session.libsession.messaging.sending_receiving.pollers.OpenGroupPolle import org.session.libsession.snode.OnionRequestAPI import org.session.libsession.utilities.AESGCM import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsignal.utilities.HTTP -import org.session.libsignal.utilities.HTTP.Verb.* -import org.session.libsignal.utilities.removing05PrefixIfNeeded -import org.session.libsignal.utilities.toHexString +import org.session.libsignal.utilities.* import org.session.libsignal.utilities.Base64.* -import org.session.libsignal.utilities.Hex -import org.session.libsignal.utilities.JsonUtil -import org.session.libsignal.utilities.Log +import org.session.libsignal.utilities.HTTP.Verb.* import org.whispersystems.curve25519.Curve25519 import java.util.* @@ -63,7 +58,7 @@ object OpenGroupAPIV2 { @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) data class CompactPollRequest(val roomID: String, val authToken: String, val fromDeletionServerID: Long?, val fromMessageServerID: Long?) - data class CompactPollResult(val messages: List, val deletions: List, val moderators: List) + data class CompactPollResult(val messages: List, val deletions: List, val moderators: List) @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) data class MessageDeletion @@ -402,7 +397,7 @@ object OpenGroupAPIV2 { val messages = parseMessages(roomID, server, rawMessages) roomID to CompactPollResult( messages = messages, - deletions = deletedServerIDs.map { it.deletedMessageId }, + deletions = deletedServerIDs, moderators = moderators ) }.toMap() diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt index 57ec540da9..3fe4ac525c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt @@ -97,15 +97,15 @@ class OpenGroupPollerV2(private val server: String, private val executorService: JobQueue.shared.add(TrimThreadJob(threadId)) } - private fun handleDeletedMessages(room: String, openGroupID: String, deletedMessageServerIDs: List) { + private fun handleDeletedMessages(room: String, openGroupID: String, deletedMessages: List) { val storage = MessagingModuleConfiguration.shared.storage val dataProvider = MessagingModuleConfiguration.shared.messageDataProvider val groupID = GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray()) val threadID = storage.getThreadId(Address.fromSerialized(groupID)) ?: return - val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { serverID -> - val messageID = dataProvider.getMessageID(serverID, threadID) + val deletedMessageIDs = deletedMessages.mapNotNull { deletion -> + val messageID = dataProvider.getMessageID(deletion.deletedMessageId, threadID) if (messageID == null) { - Log.d("Loki", "Couldn't find message ID for message with serverID: $serverID.") + Log.d("Loki", "Couldn't find message ID for message with serverID: ${deletion.deletedMessageId}.") } messageID } @@ -113,7 +113,7 @@ class OpenGroupPollerV2(private val server: String, private val executorService: MessagingModuleConfiguration.shared.messageDataProvider.deleteMessage(messageId, isSms) } val currentMax = storage.getLastDeletionServerID(room, server) ?: 0L - val latestMax = deletedMessageServerIDs.maxOrNull() ?: 0L + val latestMax = deletedMessages.map { it.id }.maxOrNull() ?: 0L if (latestMax > currentMax && latestMax != 0L) { storage.setLastDeletionServerID(room, server, latestMax) } From c6976ca3cd175bf4180a126ac56ad200360d52ec Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Fri, 28 May 2021 11:00:49 +1000 Subject: [PATCH 17/35] Fix naming confusion --- .../libsession/messaging/open_groups/OpenGroupAPIV2.kt | 6 +++--- .../sending_receiving/pollers/OpenGroupPollerV2.kt | 10 +++++----- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt index a1bcfe441c..a998f685cb 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt @@ -67,7 +67,7 @@ object OpenGroupAPIV2 { @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) data class MessageDeletion - @JvmOverloads constructor(val id: Long = 0, val deletedMessageId: Long = 0 + @JvmOverloads constructor(val id: Long = 0, val deletedMessageServerID: Long = 0 ) { companion object { @@ -396,13 +396,13 @@ object OpenGroupAPIV2 { // Deletions val type = TypeFactory.defaultInstance().constructCollectionType(List::class.java, MessageDeletion::class.java) val idsAsString = JsonUtil.toJson(json["deletions"]) - val deletedServerIDs = JsonUtil.fromJson>(idsAsString, type) ?: throw Error.ParsingFailed + val deletions = JsonUtil.fromJson>(idsAsString, type) ?: throw Error.ParsingFailed // Messages val rawMessages = json["messages"] as? List> ?: return@mapNotNull null val messages = parseMessages(roomID, server, rawMessages) roomID to CompactPollResult( messages = messages, - deletions = deletedServerIDs.map { it.deletedMessageId }, + deletions = deletions, moderators = moderators ) }.toMap() diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt index 57ec540da9..7cfc44801f 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPollerV2.kt @@ -97,15 +97,15 @@ class OpenGroupPollerV2(private val server: String, private val executorService: JobQueue.shared.add(TrimThreadJob(threadId)) } - private fun handleDeletedMessages(room: String, openGroupID: String, deletedMessageServerIDs: List) { + private fun handleDeletedMessages(room: String, openGroupID: String, deletions: List) { val storage = MessagingModuleConfiguration.shared.storage val dataProvider = MessagingModuleConfiguration.shared.messageDataProvider val groupID = GroupUtil.getEncodedOpenGroupID(openGroupID.toByteArray()) val threadID = storage.getThreadId(Address.fromSerialized(groupID)) ?: return - val deletedMessageIDs = deletedMessageServerIDs.mapNotNull { serverID -> - val messageID = dataProvider.getMessageID(serverID, threadID) + val deletedMessageIDs = deletions.mapNotNull { deletion -> + val messageID = dataProvider.getMessageID(deletion.deletedMessageServerID, threadID) if (messageID == null) { - Log.d("Loki", "Couldn't find message ID for message with serverID: $serverID.") + Log.d("Loki", "Couldn't find message ID for message with serverID: ${deletion.deletedMessageServerID}.") } messageID } @@ -113,7 +113,7 @@ class OpenGroupPollerV2(private val server: String, private val executorService: MessagingModuleConfiguration.shared.messageDataProvider.deleteMessage(messageId, isSms) } val currentMax = storage.getLastDeletionServerID(room, server) ?: 0L - val latestMax = deletedMessageServerIDs.maxOrNull() ?: 0L + val latestMax = deletions.map { it.id }.maxOrNull() ?: 0L if (latestMax > currentMax && latestMax != 0L) { storage.setLastDeletionServerID(room, server, latestMax) } From 0ef576da737fb96d63c2ba2e5925030cdc815f11 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Fri, 28 May 2021 11:02:55 +1000 Subject: [PATCH 18/35] Update version number --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 02dd1bb594..e92efb42fa 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -143,8 +143,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 177 -def canonicalVersionName = "1.10.9" +def canonicalVersionCode = 178 +def canonicalVersionName = "1.10.10" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From c0d4dd741cfb5c981927ebf58f3e3ba1f89d83ec Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 28 May 2021 11:22:06 +1000 Subject: [PATCH 19/35] debug --- .../loki/activities/CreatePrivateChatActivity.kt | 6 ++++-- .../java/org/session/libsession/snode/SnodeAPI.kt | 15 +++++++-------- 2 files changed, 11 insertions(+), 10 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt index eae640763b..101a3a90b7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt @@ -19,6 +19,7 @@ import kotlinx.android.synthetic.main.activity_create_private_chat.tabLayout import kotlinx.android.synthetic.main.activity_create_private_chat.viewPager import kotlinx.android.synthetic.main.fragment_enter_public_key.* import network.loki.messenger.R +import nl.komponents.kovenant.ui.failUi import org.session.libsession.snode.SnodeAPI import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.conversation.ConversationActivity @@ -29,6 +30,7 @@ import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsession.utilities.Util import org.session.libsignal.utilities.PublicKeyValidation @@ -83,7 +85,7 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC } fun createPrivateChatIfPossible(onsNameOrPublicKey: String) { - if (!PublicKeyValidation.isValid(onsNameOrPublicKey)) { + if (PublicKeyValidation.isValid(onsNameOrPublicKey)) { createPrivateChat(onsNameOrPublicKey) } else { // This could be an ONS name @@ -91,7 +93,7 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC SnodeAPI.getSessionIDFor(onsNameOrPublicKey).success { hexEncodedPublicKey -> hideLoader() this.createPrivateChat(hexEncodedPublicKey) - }.fail { exception -> + }.failUi { exception -> hideLoader() var message = "Please check the Session ID or ONS name and try again." exception.localizedMessage?.let { diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 5856633cb7..78878c6d3b 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -182,7 +182,7 @@ object SnodeAPI { "endpoint" to "ons_resolve", "params" to mapOf( "type" to 0, "name_hash" to base64EncodedNameHash ) ) - val promises = (0..validationCount).map { + val promises = (1..validationCount).map { getRandomSnode().bind { snode -> invoke(Snode.Method.OxenDaemonRPCCall, snode, null, parameters) } @@ -193,26 +193,25 @@ object SnodeAPI { val intermediate = json["result"] as? Map<*, *> val hexEncodedCiphertext = intermediate?.get("encrypted_value") as? String if (hexEncodedCiphertext != null) { + val ciphertext = Hex.fromStringCondensed(hexEncodedCiphertext) val isArgon2Based = (intermediate["nonce"] == null) if (isArgon2Based) { // Handle old Argon2-based encryption used before HF16 val salt = ByteArray(PwHash.SALTBYTES) - val key: String + val key: ByteArray val nonce = ByteArray(SecretBox.NONCEBYTES) - val sessionID: String + val sessionIDAsData = ByteArray(sessionIDByteCount) try { - key = sodium.cryptoPwHash(onsName, SecretBox.KEYBYTES, salt, PwHash.OPSLIMIT_MODERATE, PwHash.MEMLIMIT_MODERATE, PwHash.Alg.PWHASH_ALG_ARGON2ID13) + key = Key.fromHexString(sodium.cryptoPwHash(onsName, SecretBox.KEYBYTES, salt, PwHash.OPSLIMIT_MODERATE, PwHash.MEMLIMIT_MODERATE, PwHash.Alg.PWHASH_ALG_ARGON2ID13)).asBytes } catch (e: SodiumException) { deferred.reject(Error.HashingFailed) return@success } - try { - sessionID = sodium.cryptoSecretBoxOpenEasy(hexEncodedCiphertext, nonce, Key.fromHexString(key)) - } catch (e: SodiumException) { + if (!sodium.cryptoSecretBoxOpenEasy(sessionIDAsData, ciphertext, ciphertext.size.toLong(), nonce, key)) { deferred.reject(Error.DecryptionFailed) return@success } - sessionIDs.add(sessionID) + sessionIDs.add(Hex.toStringCondensed(sessionIDAsData)) } else { val hexEncodedNonce = intermediate["nonce"] as? String if (hexEncodedNonce == null) { From 497405fe50615adab696ce5c9431461dd4e745fb Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 28 May 2021 11:22:46 +1000 Subject: [PATCH 20/35] clean --- .../securesms/loki/activities/CreatePrivateChatActivity.kt | 1 - .../src/main/java/org/session/libsession/snode/SnodeAPI.kt | 1 - 2 files changed, 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt index 101a3a90b7..2dece4b9e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt @@ -30,7 +30,6 @@ import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragment import org.thoughtcrime.securesms.loki.fragments.ScanQRCodeWrapperFragmentDelegate import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.TextSecurePreferences -import org.session.libsession.utilities.Util import org.session.libsignal.utilities.PublicKeyValidation diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index 78878c6d3b..abcb132a2d 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -22,7 +22,6 @@ import org.session.libsignal.utilities.* import org.session.libsignal.utilities.Base64 import java.security.SecureRandom import java.util.* -import javax.crypto.AEADBadTagException object SnodeAPI { private val sodium by lazy { LazySodiumAndroid(SodiumAndroid()) } From 4f7d26d86e2c9b9474d85da3ce45277e9f95ec5e Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 28 May 2021 12:04:37 +1000 Subject: [PATCH 21/35] debug for new encryption --- .../main/java/org/session/libsession/snode/SnodeAPI.kt | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index abcb132a2d..d46ee4bcca 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -223,14 +223,12 @@ object SnodeAPI { deferred.reject(Error.HashingFailed) return@success } - val sessionID: String - try { - sessionID = sodium.decrypt(hexEncodedCiphertext, null, nonce, Key.fromBytes(key), AEAD.Method.CHACHA20_POLY1305_IETF) - } catch (e: Exception) { + val sessionIDAsData = ByteArray(sessionIDByteCount) + if (!sodium.cryptoAeadXChaCha20Poly1305IetfDecrypt(sessionIDAsData, null, null, ciphertext, ciphertext.size.toLong(), null, 0, nonce, key)) { deferred.reject(Error.DecryptionFailed) return@success } - sessionIDs.add(sessionID) + sessionIDs.add(Hex.toStringCondensed(sessionIDAsData)) } } else { deferred.reject(Error.Generic) From e8c52961aa646c1ced02690783262227a608810d Mon Sep 17 00:00:00 2001 From: ryanzhao Date: Fri, 28 May 2021 15:14:05 +1000 Subject: [PATCH 22/35] minor fix --- .../securesms/loki/activities/CreatePrivateChatActivity.kt | 5 +++-- app/src/main/res/values/strings.xml | 1 + .../src/main/java/org/session/libsession/snode/SnodeAPI.kt | 5 ++++- 3 files changed, 8 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt index 2dece4b9e6..54c80227a2 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/activities/CreatePrivateChatActivity.kt @@ -20,6 +20,7 @@ import kotlinx.android.synthetic.main.activity_create_private_chat.viewPager import kotlinx.android.synthetic.main.fragment_enter_public_key.* import network.loki.messenger.R import nl.komponents.kovenant.ui.failUi +import nl.komponents.kovenant.ui.successUi import org.session.libsession.snode.SnodeAPI import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.conversation.ConversationActivity @@ -89,12 +90,12 @@ class CreatePrivateChatActivity : PassphraseRequiredActionBarActivity(), ScanQRC } else { // This could be an ONS name showLoader() - SnodeAPI.getSessionIDFor(onsNameOrPublicKey).success { hexEncodedPublicKey -> + SnodeAPI.getSessionIDFor(onsNameOrPublicKey).successUi { hexEncodedPublicKey -> hideLoader() this.createPrivateChat(hexEncodedPublicKey) }.failUi { exception -> hideLoader() - var message = "Please check the Session ID or ONS name and try again." + var message = resources.getString(R.string.fragment_enter_public_key_error_message) exception.localizedMessage?.let { message = it } diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 80eb6fd035..b8d6c578d4 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -721,6 +721,7 @@ Enter Session ID or ONS name Users can share their Session ID by going into their account settings and tapping "Share Session ID", or by sharing their QR code. + Please check the Session ID or ONS name and try again. Session needs camera access to scan QR codes Grant Camera Access diff --git a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt index d46ee4bcca..f126871a40 100644 --- a/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt +++ b/libsession/src/main/java/org/session/libsession/snode/SnodeAPI.kt @@ -183,7 +183,10 @@ object SnodeAPI { ) val promises = (1..validationCount).map { getRandomSnode().bind { snode -> - invoke(Snode.Method.OxenDaemonRPCCall, snode, null, parameters) + retryIfNeeded(maxRetryCount) { + invoke(Snode.Method.OxenDaemonRPCCall, snode, null, parameters) + } + } } all(promises).success { results -> From 1262f80a1f0d5357478f689aba52f59f6a538710 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Fri, 28 May 2021 16:09:15 +1000 Subject: [PATCH 23/35] Clean up ProfilePictureView --- .../conversation/ConversationItem.java | 13 ++- .../loki/views/MentionCandidateView.kt | 1 - .../loki/views/ProfilePictureView.kt | 69 +++++--------- .../profile_picture_view_large_background.xml | 2 - .../profile_picture_view_large_foreground.xml | 2 - ...profile_picture_view_medium_background.xml | 2 - ...profile_picture_view_medium_foreground.xml | 2 - ...ile_picture_view_rss_medium_background.xml | 2 - .../profile_picture_view_small_background.xml | 2 - .../main/res/layout/view_profile_picture.xml | 89 ++++--------------- 10 files changed, 44 insertions(+), 140 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java index c089feed6b..344dfc93ee 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationItem.java @@ -45,9 +45,11 @@ import android.widget.ImageView; import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; + import androidx.annotation.DimenRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; + import com.annimon.stream.Stream; import org.session.libsession.messaging.contacts.Contact; @@ -58,17 +60,16 @@ import org.session.libsession.messaging.open_groups.OpenGroupV2; import org.session.libsession.messaging.sending_receiving.attachments.AttachmentTransferProgress; import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.recipients.RecipientModifiedListener; import org.session.libsession.messaging.utilities.UpdateMessageData; -import org.session.libsession.utilities.GroupUtil; +import org.session.libsession.utilities.Stub; import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.ThemeUtil; import org.session.libsession.utilities.Util; import org.session.libsession.utilities.ViewUtil; -import org.session.libsession.utilities.Stub; -import org.session.libsignal.utilities.guava.Optional; +import org.session.libsession.utilities.recipients.Recipient; +import org.session.libsession.utilities.recipients.RecipientModifiedListener; import org.session.libsignal.utilities.Log; +import org.session.libsignal.utilities.guava.Optional; import org.thoughtcrime.securesms.BindableConversationItem; import org.thoughtcrime.securesms.MediaPreviewActivity; import org.thoughtcrime.securesms.MessageDetailsActivity; @@ -88,7 +89,6 @@ import org.thoughtcrime.securesms.database.model.MmsMessageRecord; import org.thoughtcrime.securesms.database.model.Quote; import org.thoughtcrime.securesms.linkpreview.LinkPreviewUtil; import org.thoughtcrime.securesms.loki.utilities.MentionUtilities; -import org.thoughtcrime.securesms.loki.utilities.OpenGroupUtilities; import org.thoughtcrime.securesms.loki.views.MessageAudioView; import org.thoughtcrime.securesms.loki.views.OpenGroupInvitationView; import org.thoughtcrime.securesms.loki.views.ProfilePictureView; @@ -759,7 +759,6 @@ public class ConversationItem extends LinearLayout String displayName = recipient.getName(); profilePictureView.setDisplayName(displayName); profilePictureView.setAdditionalPublicKey(null); - profilePictureView.setRSSFeed(false); profilePictureView.setGlide(glideRequests); profilePictureView.update(); } diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt index 0a748c98f7..d355597bf9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/MentionCandidateView.kt @@ -34,7 +34,6 @@ class MentionCandidateView(context: Context, attrs: AttributeSet?, defStyleAttr: profilePictureView.publicKey = mentionCandidate.publicKey profilePictureView.displayName = mentionCandidate.displayName profilePictureView.additionalPublicKey = null - profilePictureView.isRSSFeed = false profilePictureView.glide = glide!! profilePictureView.update() if (openGroupServer != null && openGroupRoom != null) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt index e2de25ca90..3482088dd9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/loki/views/ProfilePictureView.kt @@ -20,17 +20,15 @@ import org.thoughtcrime.securesms.database.DatabaseFactory import org.thoughtcrime.securesms.loki.utilities.AvatarPlaceholderGenerator import org.thoughtcrime.securesms.mms.GlideRequests -// TODO: Look into a better way of handling different sizes. Maybe an enum (with associated values) encapsulating the different modes? - class ProfilePictureView : RelativeLayout { lateinit var glide: GlideRequests var publicKey: String? = null var displayName: String? = null var additionalPublicKey: String? = null var additionalDisplayName: String? = null - var isRSSFeed = false var isLarge = false - private val profilePicturesCached = mutableMapOf() + + private val profilePicturesCache = mutableMapOf() // region Lifecycle constructor(context: Context) : super(context) { @@ -79,15 +77,11 @@ class ProfilePictureView : RelativeLayout { val apk = randomUsers.getOrNull(1) ?: "" additionalPublicKey = apk additionalDisplayName = getUserDisplayName(apk) - isRSSFeed = recipient.name == "Loki News" || - recipient.name == "Session Updates" || - recipient.name == "Session Public Chat" } else { val publicKey = recipient.address.toString() this.publicKey = publicKey displayName = getUserDisplayName(publicKey) additionalPublicKey = null - isRSSFeed = false } update() } @@ -95,68 +89,47 @@ class ProfilePictureView : RelativeLayout { fun update() { val publicKey = publicKey ?: return val additionalPublicKey = additionalPublicKey - doubleModeImageViewContainer.visibility = if (additionalPublicKey != null && !isRSSFeed) { - setProfilePictureIfNeeded( - doubleModeImageView1, - publicKey, - displayName, - R.dimen.small_profile_picture_size) - setProfilePictureIfNeeded( - doubleModeImageView2, - additionalPublicKey, - additionalDisplayName, - R.dimen.small_profile_picture_size) - View.VISIBLE + if (additionalPublicKey != null) { + setProfilePictureIfNeeded(doubleModeImageView1, publicKey, displayName, R.dimen.small_profile_picture_size) + setProfilePictureIfNeeded(doubleModeImageView2, additionalPublicKey, additionalDisplayName, R.dimen.small_profile_picture_size) + doubleModeImageViewContainer.visibility = View.VISIBLE } else { glide.clear(doubleModeImageView1) glide.clear(doubleModeImageView2) - View.INVISIBLE + doubleModeImageViewContainer.visibility = View.INVISIBLE } - singleModeImageViewContainer.visibility = if (additionalPublicKey == null && !isRSSFeed && !isLarge) { - setProfilePictureIfNeeded( - singleModeImageView, - publicKey, - displayName, - R.dimen.medium_profile_picture_size) - View.VISIBLE + if (additionalPublicKey == null && !isLarge) { + setProfilePictureIfNeeded(singleModeImageView, publicKey, displayName, R.dimen.medium_profile_picture_size) + singleModeImageView.visibility = View.VISIBLE } else { glide.clear(singleModeImageView) - View.INVISIBLE + singleModeImageView.visibility = View.INVISIBLE } - largeSingleModeImageViewContainer.visibility = if (additionalPublicKey == null && !isRSSFeed && isLarge) { - setProfilePictureIfNeeded( - largeSingleModeImageView, - publicKey, - displayName, - R.dimen.large_profile_picture_size) - View.VISIBLE + if (additionalPublicKey == null && isLarge) { + setProfilePictureIfNeeded(largeSingleModeImageView, publicKey, displayName, R.dimen.large_profile_picture_size) + largeSingleModeImageView.visibility = View.VISIBLE } else { glide.clear(largeSingleModeImageView) - View.INVISIBLE + largeSingleModeImageView.visibility = View.INVISIBLE } - rssImageView.visibility = if (isRSSFeed) View.VISIBLE else View.INVISIBLE } private fun setProfilePictureIfNeeded(imageView: ImageView, publicKey: String, displayName: String?, @DimenRes sizeResId: Int) { if (publicKey.isNotEmpty()) { val recipient = Recipient.from(context, Address.fromSerialized(publicKey), false) - if (profilePicturesCached.containsKey(publicKey) && profilePicturesCached[publicKey] == recipient.profileAvatar) return + if (profilePicturesCache.containsKey(publicKey) && profilePicturesCache[publicKey] == recipient.profileAvatar) return val signalProfilePicture = recipient.contactPhoto val avatar = (signalProfilePicture as? ProfileContactPhoto)?.avatarObject if (signalProfilePicture != null && avatar != "0" && avatar != "") { glide.clear(imageView) glide.load(signalProfilePicture).diskCacheStrategy(DiskCacheStrategy.AUTOMATIC).circleCrop().into(imageView) - profilePicturesCached[publicKey] = recipient.profileAvatar + profilePicturesCache[publicKey] = recipient.profileAvatar } else { val sizeInPX = resources.getDimensionPixelSize(sizeResId) glide.clear(imageView) - glide.load(AvatarPlaceholderGenerator.generate( - context, - sizeInPX, - publicKey, - displayName - )).diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView) - profilePicturesCached[publicKey] = recipient.profileAvatar + glide.load(AvatarPlaceholderGenerator.generate(context, sizeInPX, publicKey, displayName)) + .diskCacheStrategy(DiskCacheStrategy.ALL).circleCrop().into(imageView) + profilePicturesCache[publicKey] = recipient.profileAvatar } } else { imageView.setImageDrawable(null) @@ -164,7 +137,7 @@ class ProfilePictureView : RelativeLayout { } fun recycle() { - profilePicturesCached.clear() + profilePicturesCache.clear() } // endregion } diff --git a/app/src/main/res/drawable/profile_picture_view_large_background.xml b/app/src/main/res/drawable/profile_picture_view_large_background.xml index c5f6d91a0d..278d70901f 100644 --- a/app/src/main/res/drawable/profile_picture_view_large_background.xml +++ b/app/src/main/res/drawable/profile_picture_view_large_background.xml @@ -6,6 +6,4 @@ - - \ No newline at end of file diff --git a/app/src/main/res/drawable/profile_picture_view_large_foreground.xml b/app/src/main/res/drawable/profile_picture_view_large_foreground.xml index 7d6f94a2b4..e73b68d168 100644 --- a/app/src/main/res/drawable/profile_picture_view_large_foreground.xml +++ b/app/src/main/res/drawable/profile_picture_view_large_foreground.xml @@ -6,6 +6,4 @@ - - \ No newline at end of file diff --git a/app/src/main/res/drawable/profile_picture_view_medium_background.xml b/app/src/main/res/drawable/profile_picture_view_medium_background.xml index 2b379dec4e..37c5ce3e74 100644 --- a/app/src/main/res/drawable/profile_picture_view_medium_background.xml +++ b/app/src/main/res/drawable/profile_picture_view_medium_background.xml @@ -6,6 +6,4 @@ - - \ No newline at end of file diff --git a/app/src/main/res/drawable/profile_picture_view_medium_foreground.xml b/app/src/main/res/drawable/profile_picture_view_medium_foreground.xml index e05dd6aaba..808be8d0e4 100644 --- a/app/src/main/res/drawable/profile_picture_view_medium_foreground.xml +++ b/app/src/main/res/drawable/profile_picture_view_medium_foreground.xml @@ -6,6 +6,4 @@ - - \ No newline at end of file diff --git a/app/src/main/res/drawable/profile_picture_view_rss_medium_background.xml b/app/src/main/res/drawable/profile_picture_view_rss_medium_background.xml index 9496e53ed0..a12f21fce5 100644 --- a/app/src/main/res/drawable/profile_picture_view_rss_medium_background.xml +++ b/app/src/main/res/drawable/profile_picture_view_rss_medium_background.xml @@ -6,6 +6,4 @@ - - \ No newline at end of file diff --git a/app/src/main/res/drawable/profile_picture_view_small_background.xml b/app/src/main/res/drawable/profile_picture_view_small_background.xml index d01f12f77a..2235924110 100644 --- a/app/src/main/res/drawable/profile_picture_view_small_background.xml +++ b/app/src/main/res/drawable/profile_picture_view_small_background.xml @@ -6,6 +6,4 @@ - - \ No newline at end of file diff --git a/app/src/main/res/layout/view_profile_picture.xml b/app/src/main/res/layout/view_profile_picture.xml index ba9c55df02..513d845054 100644 --- a/app/src/main/res/layout/view_profile_picture.xml +++ b/app/src/main/res/layout/view_profile_picture.xml @@ -1,97 +1,42 @@ - + android:layout_height="wrap_content"> - + android:layout_alignParentTop="true" + android:background="@drawable/profile_picture_view_small_background" /> - - - - - - - - - - - - - - - - - - - - - + android:layout_alignParentBottom="true" + android:background="@drawable/profile_picture_view_small_background" /> + android:background="@drawable/profile_picture_view_medium_background" /> - - - - - - - + android:layout_height="@dimen/large_profile_picture_size" + android:background="@drawable/profile_picture_view_large_background" /> \ No newline at end of file From f771674a084aa23e16e8a9501c64b4c90d5590b8 Mon Sep 17 00:00:00 2001 From: Archit Sharma <74408634+iArchitSharma@users.noreply.github.com> Date: Fri, 28 May 2021 18:46:54 +0700 Subject: [PATCH 24/35] Fixed typo --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index ad35159e79..c313eb9738 100644 --- a/README.md +++ b/README.md @@ -4,7 +4,7 @@ Add the [F-Droid repo](https://fdroid.getsession.org/) -[Grab the APK here](https://github.com/loki-project/session-android/releases/latest) +[Download the APK from here](https://github.com/loki-project/session-android/releases/latest) ## Summary From f2a8d50a4d7d191088fa6debe968028654f9d0c2 Mon Sep 17 00:00:00 2001 From: Archit Sharma <74408634+iArchitSharma@users.noreply.github.com> Date: Fri, 28 May 2021 19:56:20 +0700 Subject: [PATCH 25/35] All Hindi Translation done --- app/src/main/res/values-hi/strings.xml | 438 ++++++++++++------------- 1 file changed, 219 insertions(+), 219 deletions(-) diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index 2f68cdfbff..d1ba2ee827 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -3,32 +3,32 @@ सेशन हाँ नहीं - मिटाओ + मिटाएं प्रतिबंध - कृपया इंतज़ार कीजिए + कृपया इंतज़ार कीजिए... संरक्षित करें अपने लिए नोट वर्ज़न %s - नया Session सन्देश + नया मेसेज \+%d - %d बातचीत प्रति संदेश - %d बातचीत प्रति संदेश + %d संदेश प्रति बातचीत + %d संदेश प्रति बातचीत - सभी पुरानी संदेश अभी नष्ट करूँ? + सभी पुरानी संदेश अभी नष्ट करें? यह तुरंत सभी वार्तालापों को हाल ही के संदेश में ट्रिम कर देगा। - यह तुरंत सभी वार्तालापों को हाल ही के संदेश में ट्रिम कर देगा।%d + यह तुरंत सभी वार्तालापों को हाल ही के %d संदेशों में ट्रिम कर देगा। - नष्ट करो + नष्ट करें पर - बंद + ऑफ (तस्वीर) - (श्रव्य) + (ऑडियो) (वीडियो) ( उत्तर ) @@ -64,34 +64,34 @@ मैसेज लिखें %s तक मौन किया - %1$d members - Community Guidelines + %1$dसदस्य + सामुदायिक दिशानिर्देश अवैध प्राप्तकर्ता होम स्क्रीन में जोड़ा गया समूह छोड़ दें? क्या आप वाकई इस समूह को छोड़ना चाहते हैं? समूह छोड़ने में त्रुटि - इस संपर्क को अनवरोधित करें? + इस संपर्क को अनब्लॉक करें? आप एक बार फिर से इस संपर्क से संदेश और कॉल प्राप्त करने में सक्षम होंगे। अनब्लॉक करें - आपके द्वारा भेजे जा रहे संदेश के प्रकार के लिए अनुलग्नक आकार सीमा से अधिक है। + आपके द्वारा भेजा जा रहा संदेश आकार सीमा से अधिक है। ऑडियो रिकॉर्ड करने में असमर्थ आपके डिवाइस पर इस लिंक को संभालने के लिए कोई ऐप उपलब्ध नहीं है। - Add members - Join %s - Are you sure you want to join the %s open group? - ऑडियो संदेश भेजने के लिए, अपने माइक्रोफ़ोन पर Session पहुंच की अनुमति दें। + सदस्य जोड़ें + %s से जुड़ें + क्या आप %s open ग्रुप से जुड़ना चाहते हैं? + ऑडियो संदेश भेजने के लिए माइक्रोफोन की अनुमति Session को दें। ऑडियो संदेशों को भेजने के लिए Session को माइक्रोफ़ोन अनुमति की आवश्यकता होती है, लेकिन इसे स्थायी रूप से अस्वीकार कर दिया गया है। कृपया ऐप सेटिंग्स जारी रखें, \"अनुमतियां\" चुनें, और \"माइक्रोफ़ोन\" सक्षम करें। - फोटो और वीडियो कैप्चर करने के लिए, कैमरे को Session पहुंच की अनुमति दें। + फोटो और वीडियो कैप्चर करने के लिए Session को कैमरे की अनुमति दें। Session को फ़ोटो या वीडियो लेने के लिए कैमरा अनुमति की आवश्यकता होती है, लेकिन इसे स्थायी रूप से अस्वीकार कर दिया गया है। कृपया ऐप सेटिंग्स जारी रखें, \"अनुमतियां\" चुनें, और \"कैमरा\" सक्षम करें। - Session को फ़ोटो या वीडियो लेने के लिए कैमरा अनुमतियां चाहिए - %1$s%2$s - %1$d of %2$d + Session को फ़ोटो या वीडियो लेने के लिए कैमरा अनुमति चाहिए + %1$s %2$s + %2$d का %1$d कोई परिणाम नहीं %d अपठित संदेश - %dअपठित संदेश + %d अपठित संदेश @@ -102,32 +102,32 @@ यह सभी चयनित वार्तालापों को स्थायी रूप से हटा देगा। यह सभी %1$d चयनित वार्तालापों को स्थायी रूप से हटा देगा। - Ban this user? - इसे भंडारण में सहेजें? + सदस्य को बैन करें? + इसे स्टोरेज में सहेजें? - सभी मीडिया को स्टोरेज में सहेजने से आपके डिवाइस पर किसी अन्य ऐप को एक्सेस करने की अनुमति मिल जाएगी। \n\n जारी रखें? - सभी %1$d मीडिया को स्टोरेज में सहेजने से आपके डिवाइस पर किसी अन्य ऐप को एक्सेस करने की अनुमति मिल जाएगी। \n\n जारी रखें? + इस मीडिया को स्टोरेज में सहेजने से आपके डिवाइस पर किसी अन्य ऐप को इसे एक्सेस करने की अनुमति मिल जाएगी। \n\n जारी रखें? + सभी %1$d मीडिया को स्टोरेज में सहेजने से आपके डिवाइस पर किसी अन्य ऐप को इन्हें एक्सेस करने की अनुमति मिल जाएगी। \n\n जारी रखें? - संग्रहण में संलग्नक सहेजते समय त्रुटि! - संग्रहण में संलग्नक सहेजते समय त्रुटि! + संग्रहण में संलग्नक को सहेजते समय त्रुटि! + संग्रहण में संलग्नक को सहेजते समय त्रुटि! - अनुलग्नक सहेजा जा रहा है - %1$dअनुलग्नक सहेजा जा रहा है + अटैचमेंट सहेजा जा रहा है + %1$d अटैचमेंट सहेजे जा रहे हैं - स्टोरेज में अनुलग्नक सहेजा जा रहा है - स्टोरेज में %1$d अनुलग्नक सहेज रहा है + स्टोरेज में अटैचमेंट सहेजा जा रहा है + स्टोरेज में %1$d अटैचमेंट सहेज रहे हैं - रूका हुआ + बाकी... डेटा (Session) एमएमएस एसएमएस हटा दिया जा रहा है संदेश हटाए जा रहे हैं - Banning - Banning user… + बैन कर रहे हैं + सदस्य को बैन कर रहे हैं... मूल संदेश नहीं मिला मूल संदेश अब उपलब्ध नहीं है @@ -150,16 +150,16 @@ पूर्ण रिज़ॉल्यूशन जीआईएफ पुनर्प्राप्त करते समय त्रुटि - जीआईएफ + GIFs स्टिकर - Photo + तस्वीर वॉइस संदेश रिकॉर्ड करने के लिए टैप करें और दबाएं और भेजने के लिए रिलीज़ करें - Unable to find message - Message from %1$s - Your message + संदेश ढूंढने में असमर्थ + %1$s से संदेश + आपका संदेश मीडिया @@ -174,21 +174,21 @@ संदेश हटाए जा रहे हैं दस्तावेज़ सभी को चुन लो स` - Collecting attachments... + अटैचमेंट्स इकट्ठे कर रहे हैं... मल्टीमीडिया संदेश एमएमएस संदेश डाउनलोड किया जा रहा है एमएमएस संदेश डाउनलोड करने में त्रुटि, पुनः प्रयास करने के लिए टैप करें - Send to %s + %s को भेजें - Add a caption... - An item was removed because it exceeded the size limit - Camera unavailable. - Message to %s + शीर्षक जोड़ें... + एक वस्तु को आकार बड़ा होने कि वजह से हटाया गया + कैमरा अनुपलब्ध. + %s को संदेश - You can\'t share more than %d item. - You can\'t share more than %d items. + आप %d से ज्यादा चीजें शेयर नहीं कर सकते + आप %d से ज्यादा चीजें शेयर नहीं कर सकते सभी मीडिया @@ -216,7 +216,7 @@ वीडियो दूषित कुंजी प्राप्त की -         संदेश का आदान-प्रदान करें! + संदेश का आदान-प्रदान करें! अमान्य प्रोटोकॉल संस्करण के लिए कुंजी एक्सचेंज संदेश प्राप्त हुआ। नए सुरक्षा नंबर के साथ संदेश प्राप्त हुआ। प्रक्रिया और प्रदर्शित करने के लिए टैप करें। आपने सुरक्षित सत्र रीसेट कर दिया है। @@ -234,14 +234,14 @@ %sअब Session पर हैं! संदेश जो गायब हो जाते हैं अक्षम गायब संदेश समय %s तक सेट हो गया - %s took a screenshot. - Media saved by %s. + %s ने एक स्क्रीनशॉट लिया. + %s द्वारा मीडिया सेव किया गया। सुरक्षा संख्या बदल गई %s के साथ आपका सुरक्षा नंबर बदल गया है। आपने सत्यापित चिह्नित किया है आपने असत्यापित चिह्नित किया है - This conversation is empty - Open group invitation + यह वार्तालाप खाली है + ग्रुप आमंत्रण खोलें Session अपडेट Session का एक नया संस्करण उपलब्ध है, अपडेट करने के लिए टैप करें @@ -250,7 +250,7 @@ गैर-मौजूदा सत्र के लिए संदेश एन्क्रिप्ट किया गया खराब एन्क्रिप्टेड एमएमएस संदेश - गैर-मौजूदा सत्र के लिए एमएमएस संदेश एन्क्रिप्ट किया गया  + गैर-मौजूदा सत्र के लिए एमएमएस संदेश एन्क्रिप्ट किया गया नोटिफिकेशन म्यूट करें @@ -294,18 +294,18 @@ संदेश भेजने में दिक्कत हुआ %s के लिए सहेजा गया - Saved + सेव किया गया सर्च अमान्य शॉर्टकट - Session + सैशन नया Session सन्देश - %d Item - %d Items + %d वस्तु + %d वस्तुएं वीडियो खोलने में त्रुटि @@ -334,10 +334,10 @@ अटैचमेंट थंबनेल त्वरित कैमरा अनुलग्नक दराज टॉगल करें रिकॉर्ड करें और ऑडियो अटैचमेंट भेजें - Lock recording of audio attachment + ऑडियो अटैचमेंट की रिकॉर्डिंग लॉक करें एसएमएस के लिए Session सक्षम करें - Slide to cancel + रद्द करने के लिए स्लाइड करें रद्द करें मीडिया संदेश @@ -354,11 +354,11 @@ रोको डाउनलोड - Join - Open group invitation - Pinned message - Community guidelines - Read + ज्वॉइन करें + ग्रुप आमंत्रण खोलें + पिन किया संदेश + सामुदायिक निर्देश + पढ़ें ऑडियो वीडियो @@ -426,8 +426,8 @@ दर्ज कुंजी भेजता है एंटर कुंजी दबाकर टेक्स्ट संदेश भेजे जाएंगे - Send link previews - Previews are supported for Imgur, Instagram, Pinterest, Reddit, and YouTube links + लिंक प्रिव्यू भेजें + Imgur, Instagram, Pinterest, Reddit, और YouTube के लिए लिंक प्रिव्यू उपलब्ध हैं स्क्रीन सुरक्षा रीसेंट सूची में और ऐप के अंदर स्क्रीनशॉट ब्लॉक करें सूचनाएं @@ -464,8 +464,8 @@ गुप्त कीबोर्ड पढ़ने की रसीदें यदि रीड रसीद अक्षम हैं, तो आप दूसरों से पढ़ने की रसीदें नहीं देख पाएंगे। - Typing indicators - If typing indicators are disabled, you won\'t be able to see typing indicators from others. + टाइपिंग सूचक + अगर टाइपिंग सूचक बंद हैं तो आप दूसरों के टाइपिंग सूचक नहीं देख पाएंगे। व्यक्तिगत सीखने को अक्षम करने के लिए कीबोर्ड को अनुरोध करें रोशनी अँधेरा @@ -488,7 +488,7 @@ संदेश का विवरण कॉपी टेक्स्ट डिलीट मेसिज - Ban user + प्रतिबंध उपयोगकर्ता संदेश दोबारा भेजें संदेश का जवाब @@ -531,7 +531,7 @@ बैकअप को बाहरी स्टोरेज में सहेजा जाएगा और नीचे पासफ्रेज से एन्क्रिप्ट किया जाएगा। बैकअप को पुनर्स्थापित करने के लिए आपके पास यह पासफ्रेज़ होना चाहिए। मैंने इस पासफ्रेज को लिखा है। इसके बिना, मैं बैकअप को पुनर्स्थापित करने में असमर्थ हूं। छोड़ दे - Cannot import backups from newer versions of Session + सेशन के नए वर्जन से बैकअप लाने में असमर्थ गलत बैकअप पासफ्रेज स्थानीय बैकअप सक्षम करें? बैकअप सक्षम करें @@ -548,153 +548,153 @@ स्क्रीन लॉक निष्क्रियता टाइमआउट कुछ नहीं - Copy public key + पब्लिक की कॉपी करें - Continue - Copy - Invalid URL - Copied to clipboard - Next - Share - Invalid Session ID - Cancel - Your Session ID - Your Session begins here... - Create Session ID - Continue Your Session - What\'s Session? - It\'s a decentralized, encrypted messaging app - So it doesn\'t collect my personal information or my conversation metadata? How does it work? - Using a combination of advanced anonymous routing and end-to-end encryption technologies. - Friends don\'t let friends use compromised messengers. You\'re welcome. - Say hello to your Session ID - Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design. - Restore your account - Enter the recovery phrase that was given to you when you signed up to restore your account. - Enter your recovery phrase - Pick your display name - This will be your name when you use Session. It can be your real name, an alias, or anything else you like. - Enter a display name - Please pick a display name - Please pick a shorter display name - Recommended - Please Pick an Option - You don\'t have any contacts yet - Start a Session - Are you sure you want to leave this group? - "Couldn't leave group" - Are you sure you want to delete this conversation? - Conversation deleted - Your Recovery Phrase - Meet your recovery phrase - Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don\'t give it to anyone. - Hold to reveal - You\'re almost finished! 80% - Secure your account by saving your recovery phrase - Tap and hold the redacted words to reveal your recovery phrase, then store it safely to secure your Session ID. - Make sure to store your recovery phrase in a safe place - Path - Session hides your IP by bouncing your messages through several Service Nodes in Session\'s decentralized network. These are the countries your connection is currently being bounced through: - You - Entry Node - Service Node - Destination - Learn More - New Session - Enter Session ID - Scan QR Code - Scan a user\'s QR code to start a session. QR codes can be found by tapping the QR code icon in account settings. - Enter Session ID of recipient - Users can share their Session ID by going into their account settings and tapping \"Share Session ID\", or by sharing their QR code. - Session needs camera access to scan QR codes - Grant Camera Access - New Closed Group - Enter a group name - You don\'t have any contacts yet - Start a Session - Please enter a group name - Please enter a shorter group name - Please pick at least 1 group member - A closed group cannot have more than 100 members - Join Open Group - Couldn\'t join group - Open Group URL - Scan QR Code - Scan the QR code of the open group you\'d like to join - Enter an open group URL - Settings - Enter a display name - Please pick a display name - Please pick a shorter display name - Privacy - Notifications - Chats - Devices - Invite - Recovery Phrase - Clear Data - Help us Translate Session - Notifications - Notification Style - Notification Content - Privacy - Chats - Notification Strategy - Change name - Unlink device - Your Recovery Phrase - This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device. - Clear All Data - This will permanently delete your messages, sessions, and contacts. - QR Code - View My QR Code - Scan QR Code - Scan someone\'s QR code to start a conversation with them - Scan Me - This is your QR code. Other users can scan it to start a session with you. - Share QR Code - Contacts - Closed Groups - Open Groups - You don\'t have any contacts yet + जारी रखें + कॉपी करें + अमान्य यूआरएल + क्लिपबोर्ड पर कॉपी किया गया है + अगला + साझा करें + अमान्य सेशन आईडी + रद्द करें + आपकी सेशन आईडी + आपका सेशन यहां आरंभ होता है... + सेशन आईडी बनाएं + अपना सेशन जारी रखें + सेशन क्या है? + यह एक decentralized, encrypted मेसेजिंग ऐप है + इसलिए यह मेरी निजी जानकारी या बातचीत का metadata इकठ्ठा नहीं करता? यह कैसे काम करता है? + एडवांस्ड anonymous routing और end-to-end encryption के मिश्रण का प्रयोग करके। + दोस्त दोस्तों को असुरक्षित मैसेंजर नहीं उपयोग करने देते। आपका स्वागत है। + अपनी सेशन आईडी को हेल्लो कहें + आपकी Session ID एक ऐसा अनोखा पता है जिसका उपयोग करके लोग आपसे Session पर संपर्क कर सकते हैं। आपकी वास्तविक पहचान से कोई संबंध नहीं होने के कारण, आपका Session ID डिज़ाइन द्वारा पूरी तरह से गुमनाम और निजी है | + अकाउंट रिस्टोर करें + अकाउंट बनाते समय बनाया गया अपना रिकवरी वाक्य डालें। + अपना पुनर्प्राप्ति वाक्यांश लिखें + अपना प्रदर्शन नाम चुनें + यह सेशन इस्तेमाल करते समय आपका नाम होगा। यह आपका असली नाम, उपनाम या कुछ और भी हो सकता है। + डिस्प्ले नाम डालें + कृपया एक डिस्प्ले नाम चुनें + कृपया एक छोटा डिसप्ले नाम चुनें + सुझाया गया + कृपया एक विकल्प चुनें। + अभी तक आपके पास कोई कॉन्टैक्ट्स नहीं हैं + एक सेशन प्रारंभ करें + क्या आप यह ग्रुप छोड़ना चाहते हैं? + "ग्रुप नहीं छोड़ सके" + क्या वाकई आप इस वार्तालाप को हटाना चाहते हैं? + बातचीत हटाई गई + आपका पुनर्प्राप्ति वाक्यांश + अपने पुनर्प्राप्ति वाक्यांश से मिलें + आपका रिकवरी फ्रेज आपके Session Id की मास्टर कीय है — आप इसका इस्तेमाल अपनी Session ID को वापिस पाने के लिए कर सकते है अगर आपका फ़ोन गुम हो गया है | अपने रिकवरी फ्रेज को एक सुरक्षित जगह रखे और इसे किसी के साथ शेयर न करे | + देखने के लिए दबाएं + पूर्ण होने वाला है! 80% + अकाउंट सुरक्षित रखने के लिए अपनी पुनर्व्यप्ती वाक्यांश को सेव करके रखें + अपना पुनर्प्राप्ति वाक्यांश प्रकट करने के लिए संशोधित शब्दों को टैप और होल्ड करें, फिर अपनी Session ID को सुरक्षित करने के लिए इसे सुरक्षित रूप से संग्रहीत करें। + अपने रिकवरी फ्रेज को एक सुरक्षित जगह रखे + पथ + सेशन आपके मैसेज को सेशन के डेंटरलिज़्ड नेटवर्क की बहुत साडी सर्विस नोड्स में भेज देता है जो आपकी IP को छुपाती है. + आप + प्रवेश नोड + सर्विस नोड + गंतव्य + अधिक जानें + नया सेशन + सेशन आईडी डालें + QR कोड को स्कैन करें + सेशन शुरू करने के लिए यूजर के क्यूआर कोड को स्कैन करें। क्यूआर कोड को अकाउंट सेटिंग में क्यूआर कोड आइकन पर टैप करके पाया जा सकता है। + रेसिपिएंट का Session आईडी दर्ज करें + यूजर अपनी अकाउंट सेटिंग में जाकर और \"शेयर Session आईडी\" टैप करके, या अपना क्यूआर कोड शेयर कर सकते है। + क्यूआर कोड स्कैन करने के लिए Session को कैमरा एक्सेस की आवश्यकता है + कैमरा केउपयोग को प्रदान करें + नया Closed Group + ग्रुप का नाम डालें + अभी तक आपके पास कोई कॉन्टैक्ट्स नहीं हैं + एक सेशन प्रारंभ करें + कृपया ग्रुप नाम डालें + कृपया एक छोटा ग्रुप नाम डालें + कृपया कम से कम एक ग्रुप सदस्य चुनें + एक क्लोज्ड ग्रुप में 100 से अधिक सदस्य नहीं हो सकते हैं + Open ग्रुप से जुड़ें + ग्रुप नहीं ज्वाइन कर सके + ग्रुप URL खोलें + QR कोड को स्कैन करें + उस ओपन ग्रुप का क्यूआर कोड स्कैन करें जिसमें आप शामिल होना चाहते हैं + Open ग्रुप URL दर्ज करें + सेटिंग्स + डिस्प्ले नाम डालें + कृपया एक डिस्प्ले नाम चुनें + कृपया एक छोटा डिसप्ले नाम चुनें + गोपनियता + सूचनाएं + चैट + डिवाइसेज़ + आमंत्रण + पुनर्प्राप्ति वाक्यांश + डेटा हटाएं + सेशन का अनुवाद करने में सहायता करें + सूचनाएं + अधिसूचना शैली + सूचना विषय सूची + गोपनियता + चैट + सूचना रणनीति + नाम बदलें + डिवाइस को अनलिंक करें + आपका पुनर्प्राप्ति वाक्यांश + यह आपका रिकवरी फ्रेज है | इसके साथ, आप अपनी Session ID को किसी नए डिवाइस पर माइग्रेट कर सकते हैं। + सभी डेटा हटाएं + यह आपके मैसेजस, सेशन और कॉन्टैक्टस को स्थायी रूप से हटा देगा। + QR कोड + मेरा QR कोड देखें + QR कोड को स्कैन करें + किसी के साथ बातचीत शुरू करने के लिए उसका क्यूआर कोड स्कैन करें + मुझे स्कैन करें + यह आपका क्यूआर कोड है। अन्य उपयोगकर्ता आपके साथ सेशन शुरू करने के लिए इसे स्कैन कर सकते हैं। + QR कोड साझा करे + संपर्क + बंद ग्रुप + खुले ग्रुप + अभी तक आपके पास कोई कॉन्टैक्ट्स नहीं हैं - Apply - Done - Edit Group - Enter a new group name - Members - Add members - Group name can\'t be empty - Please enter a shorter group name - Groups must have at least 1 group member - Remove user from group - Select Contacts - Secure session reset done - Theme - Day - Night - System default - Copy Session ID - Attachment - Voice Message - Details - Failed to activate backups. Please try again or contact support. - Restore backup - Select a file - Select a backup file and enter the passphrase it was created with. - 30-digit passphrase - This is taking a while, would you like to skip? - Link a Device - Or join one of these… - Message Notifications - There are two ways Session can notify you of new messages. - Fast Mode - Slow Mode - You’ll be notified of new messages reliably and immediately using Google’s notification servers. - Session will occasionally check for new messages in the background. - Recovery Phrase - Session is Locked - Tap to Unlock - Enter a nickname - Invalid public key + लागू करें + पूरा हुआ + ग्रुप बदलें + ग्रुप का नया नाम डालें + सदस्य + सदस्य जोड़ें + ग्रुप नाम रिक्त नहीं हो सकता। + कृपया एक छोटा ग्रुप नाम डालें + ग्रुप में कम से कम 1 सदस्य होना चाहिए + उपयोगकर्ता को ग्रुप से निकालें + कांटेक्ट चुनें + सुरक्षित सेशन रीसेट कर दिया गया है + थीम + हलका + अंधेरा + सिस्टम डिफ़ॉल्ट का इस्तेमाल करें + कापी सेशन आईडी + अटैचमेंट + वौइस् मैसेज + विस्तार + बैकअप सक्रिय करने में विफल। कृपया पुन: प्रयास करें या समर्थन से संपर्क करें। + बैकअप रिस्टोर करें + फाइल चुनें + एक बैकअप फ़ाइल चुनें और वह पासफ़्रेज़ दर्ज करें जिसके साथ इसे बनाया गया था। + 30 अंक का पासफ्रेस + यह समय ले रहा है, इसे छोड़ दें? + डिवाइस को लिंक करें + या इनमें से एक को जोड़ें... + संदेश सूचनाएं + सेशन आपको नए संदेश के बारे में दो तरीकों से बता सकता है + फास्ट मोड + स्लो मोड + आपको नई सूचनाओं के बारे में google के नोटीफिकेशन servers से तत्काल सूचित किया जाएगा। + Session कभी-कभी पृष्ठभूमि में नए संदेशों की जांच करेगा। + पुनर्प्राप्ति वाक्यांश + Session लॉक है + अनलॉक करने के लिए टैप करें + उपनाम चुनें + अमान्य सार्वजनिक कुंजी From f872c2b9be90b4ee13fad30097c6e07959cdeab1 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 31 May 2021 11:23:37 +1000 Subject: [PATCH 26/35] Prepare for OGS timestamp handling changes --- .../securesms/service/ExpiringMessageManager.java | 1 + .../libsession/messaging/open_groups/OpenGroupAPIV2.kt | 5 ++++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java index 59545c0fa0..c2c4834cd5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java +++ b/app/src/main/java/org/thoughtcrime/securesms/service/ExpiringMessageManager.java @@ -178,6 +178,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM } private class LoadTask implements Runnable { + public void run() { SmsDatabase.Reader smsReader = smsDatabase.readerFor(smsDatabase.getExpirationStartedMessages()); MmsDatabase.Reader mmsReader = mmsDatabase.getExpireStartedMessages(); diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt index 314216066c..0566eeeba0 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt @@ -232,7 +232,10 @@ object OpenGroupAPIV2 { return send(request).map { json -> @Suppress("UNCHECKED_CAST") val rawMessage = json["message"] as? Map ?: throw Error.ParsingFailed - OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.ParsingFailed + val result = OpenGroupMessageV2.fromJSON(rawMessage) ?: throw Error.ParsingFailed + val storage = MessagingModuleConfiguration.shared.storage + storage.addReceivedMessageTimestamp(result.sentTimestamp) + result } } // endregion From e1f3362c2a7941228104c35d36fa7404708ac46e Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 31 May 2021 13:13:25 +1000 Subject: [PATCH 27/35] Fix crash --- .../MultipleRecipientNotificationBuilder.java | 44 ++++++++++--------- .../SingleRecipientNotificationBuilder.java | 41 +++++++---------- .../org/session/libsession/utilities/Util.kt | 1 + 3 files changed, 41 insertions(+), 45 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java index f094cea12c..5165e87ff9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/MultipleRecipientNotificationBuilder.java @@ -4,18 +4,21 @@ import android.app.Notification; import android.app.PendingIntent; import android.content.Context; import android.content.Intent; +import android.text.SpannableStringBuilder; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.app.NotificationCompat; -import android.text.SpannableStringBuilder; + import org.session.libsession.messaging.contacts.Contact; +import org.session.libsession.utilities.NotificationPrivacyPreference; +import org.session.libsession.utilities.TextSecurePreferences; +import org.session.libsession.utilities.Util; +import org.session.libsession.utilities.recipients.Recipient; import org.thoughtcrime.securesms.database.DatabaseFactory; import org.thoughtcrime.securesms.loki.activities.HomeActivity; import org.thoughtcrime.securesms.loki.database.SessionContactDatabase; -import org.session.libsession.utilities.NotificationPrivacyPreference; -import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsession.utilities.TextSecurePreferences; -import org.session.libsession.utilities.Util; + import java.util.LinkedList; import java.util.List; @@ -50,14 +53,7 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu public void setMostRecentSender(Recipient recipient, Recipient threadRecipient) { String displayName = recipient.toShortString(); if (threadRecipient.isOpenGroupRecipient()) { - SessionContactDatabase contactDB = DatabaseFactory.getSessionContactDatabase(context); - String sessionID = recipient.getAddress().serialize(); - Contact contact = contactDB.getContactWithSessionID(sessionID); - if (contact != null) { - displayName = contact.displayName(Contact.ContactContext.OPEN_GROUP); - } else { - displayName = sessionID; - } + displayName = getOpenGroupDisplayName(recipient); } if (privacy.isDisplayContact()) { setContentText(context.getString(R.string.MessageNotifier_most_recent_from_s, displayName)); @@ -79,14 +75,7 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu public void addMessageBody(@NonNull Recipient sender, Recipient threadRecipient, @Nullable CharSequence body) { String displayName = sender.toShortString(); if (threadRecipient.isOpenGroupRecipient()) { - SessionContactDatabase contactDB = DatabaseFactory.getSessionContactDatabase(context); - String sessionID = sender.getAddress().serialize(); - Contact contact = contactDB.getContactWithSessionID(sessionID); - if (contact != null) { - displayName = contact.displayName(Contact.ContactContext.OPEN_GROUP); - } else { - displayName = sessionID; - } + displayName = getOpenGroupDisplayName(sender); } if (privacy.isDisplayMessage()) { SpannableStringBuilder builder = new SpannableStringBuilder(); @@ -118,4 +107,17 @@ public class MultipleRecipientNotificationBuilder extends AbstractNotificationBu return super.build(); } + + /** + * @param recipient the * individual * recipient for which to get the open group display name. + */ + private String getOpenGroupDisplayName(Recipient recipient) { + SessionContactDatabase contactDB = DatabaseFactory.getSessionContactDatabase(context); + String sessionID = recipient.getAddress().serialize(); + Contact contact = contactDB.getContactWithSessionID(sessionID); + if (contact == null) { return sessionID; } + String displayName = contact.displayName(Contact.ContactContext.OPEN_GROUP); + if (displayName == null) { return sessionID; } + return displayName; + } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java index 008779301b..0b38783be8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/SingleRecipientNotificationBuilder.java @@ -119,18 +119,8 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); if (privacy.isDisplayContact() && threadRecipients.isOpenGroupRecipient()) { - String displayName; - SessionContactDatabase contactDB = DatabaseFactory.getSessionContactDatabase(context); - String sessionID = individualRecipient.getAddress().serialize(); - Contact contact = contactDB.getContactWithSessionID(sessionID); - if (contact != null) { - displayName = contact.displayName(Contact.ContactContext.OPEN_GROUP); - } else { - displayName = sessionID; - } - if (displayName != null) { - stringBuilder.append(Util.getBoldedString(displayName + ": ")); - } + String displayName = getOpenGroupDisplayName(individualRecipient); + stringBuilder.append(Util.getBoldedString(displayName + ": ")); } if (privacy.isDisplayMessage()) { @@ -215,18 +205,8 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil SpannableStringBuilder stringBuilder = new SpannableStringBuilder(); if (privacy.isDisplayContact() && threadRecipient.isOpenGroupRecipient()) { - String displayName; - SessionContactDatabase contactDB = DatabaseFactory.getSessionContactDatabase(context); - String sessionID = individualRecipient.getAddress().serialize(); - Contact contact = contactDB.getContactWithSessionID(sessionID); - if (contact != null) { - displayName = contact.displayName(Contact.ContactContext.OPEN_GROUP); - } else { - displayName = sessionID; - } - if (displayName != null) { - stringBuilder.append(Util.getBoldedString(displayName + ": ")); - } + String displayName = getOpenGroupDisplayName(individualRecipient); + stringBuilder.append(Util.getBoldedString(displayName + ": ")); } if (privacy.isDisplayMessage()) { @@ -342,4 +322,17 @@ public class SingleRecipientNotificationBuilder extends AbstractNotificationBuil String displayName = recipient.getName(); return AvatarPlaceholderGenerator.generate(context, 128, publicKey, displayName); } + + /** + * @param recipient the * individual * recipient for which to get the open group display name. + */ + private String getOpenGroupDisplayName(Recipient recipient) { + SessionContactDatabase contactDB = DatabaseFactory.getSessionContactDatabase(context); + String sessionID = recipient.getAddress().serialize(); + Contact contact = contactDB.getContactWithSessionID(sessionID); + if (contact == null) { return sessionID; } + String displayName = contact.displayName(Contact.ContactContext.OPEN_GROUP); + if (displayName == null) { return sessionID; } + return displayName; + } } diff --git a/libsession/src/main/java/org/session/libsession/utilities/Util.kt b/libsession/src/main/java/org/session/libsession/utilities/Util.kt index 14a2cc9001..1ad209d5d5 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/Util.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/Util.kt @@ -321,6 +321,7 @@ object Util { @JvmStatic fun getBoldedString(value: String?): CharSequence { + if (value.isNullOrEmpty()) { return "" } val spanned = SpannableString(value) spanned.setSpan(StyleSpan(Typeface.BOLD), 0, spanned.length, From 1e1e3bf0e8cbfd29b3aad10df881764eabad6788 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 31 May 2021 13:13:50 +1000 Subject: [PATCH 28/35] Update version number --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index e92efb42fa..66de3a66d4 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -143,8 +143,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 178 -def canonicalVersionName = "1.10.10" +def canonicalVersionCode = 179 +def canonicalVersionName = "1.10.11" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From 2bdb4d18a222ae65fc800db7800a9f979915cd87 Mon Sep 17 00:00:00 2001 From: Harris Date: Mon, 31 May 2021 14:24:43 +1000 Subject: [PATCH 29/35] fix: property name for json deserialisation --- .../libsession/messaging/open_groups/OpenGroupAPIV2.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt index 0566eeeba0..158b61e7ee 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/open_groups/OpenGroupAPIV2.kt @@ -1,5 +1,6 @@ package org.session.libsession.messaging.open_groups +import com.fasterxml.jackson.annotation.JsonProperty import com.fasterxml.jackson.databind.PropertyNamingStrategy import com.fasterxml.jackson.databind.annotation.JsonNaming import com.fasterxml.jackson.databind.type.TypeFactory @@ -60,9 +61,11 @@ object OpenGroupAPIV2 { data class CompactPollRequest(val roomID: String, val authToken: String, val fromDeletionServerID: Long?, val fromMessageServerID: Long?) data class CompactPollResult(val messages: List, val deletions: List, val moderators: List) - @JsonNaming(PropertyNamingStrategy.SnakeCaseStrategy::class) - data class MessageDeletion - @JvmOverloads constructor(val id: Long = 0, val deletedMessageServerID: Long = 0 + data class MessageDeletion( + @JsonProperty("id") + val id: Long = 0, + @JsonProperty("deleted_message_id") + val deletedMessageServerID: Long = 0 ) { companion object { From b8a85c35c46d62dc52b0ca17f68e8020b182005b Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Mon, 31 May 2021 14:31:36 +1000 Subject: [PATCH 30/35] Update version number --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 66de3a66d4..6d2b81e0be 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -143,8 +143,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.2' } -def canonicalVersionCode = 179 -def canonicalVersionName = "1.10.11" +def canonicalVersionCode = 180 +def canonicalVersionName = "1.10.12" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From d3776ae0e637ccf5ca440a8cb4ae5cf8182d0150 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 1 Jun 2021 10:07:37 +1000 Subject: [PATCH 31/35] Enable data extraction notifications --- .../securesms/MediaOverviewActivity.java | 8 +------- .../securesms/MediaPreviewActivity.java | 18 +++++++----------- .../conversation/ConversationFragment.java | 10 ++-------- 3 files changed, 10 insertions(+), 26 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java index 7b25c4f1bb..53a909c5ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaOverviewActivity.java @@ -353,11 +353,9 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { saveTask.executeOnExecutor(THREAD_POOL_EXECUTOR, attachments.toArray(new SaveAttachmentTask.Attachment[attachments.size()])); actionMode.finish(); - // Sending a Data extraction notification (for incoming attachments only) boolean containsIncoming = mediaRecords.parallelStream().anyMatch(m -> !m.isOutgoing()); if (containsIncoming) { - //TODO uncomment line below when Data extraction will be activated - //sendMediaSavedNotificationIfNeeded(); + sendMediaSavedNotificationIfNeeded(); } } }.execute(); @@ -366,11 +364,7 @@ public class MediaOverviewActivity extends PassphraseRequiredActionBarActivity { }, mediaRecords.size()); } - /** - * Send a MediaSaved notification to the recipient - */ private void sendMediaSavedNotificationIfNeeded() { - // we don't send media saved notification for groups if (recipient.isGroupRecipient()) return; DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); MessageSender.send(message, recipient.getAddress()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java index 431d0b4e3f..3f8f03fa51 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java +++ b/app/src/main/java/org/thoughtcrime/securesms/MediaPreviewActivity.java @@ -39,8 +39,7 @@ import android.view.Window; import android.widget.FrameLayout; import android.widget.TextView; import android.widget.Toast; -import org.session.libsession.messaging.messages.control.DataExtractionNotification; -import org.session.libsession.messaging.sending_receiving.MessageSender; + import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; @@ -53,11 +52,14 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.viewpager.widget.PagerAdapter; import androidx.viewpager.widget.ViewPager; + +import org.session.libsession.messaging.messages.control.DataExtractionNotification; +import org.session.libsession.messaging.sending_receiving.MessageSender; import org.session.libsession.messaging.sending_receiving.attachments.DatabaseAttachment; import org.session.libsession.utilities.Address; +import org.session.libsession.utilities.Util; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.RecipientModifiedListener; -import org.session.libsession.utilities.Util; import org.session.libsignal.utilities.Log; import org.thoughtcrime.securesms.components.MediaView; import org.thoughtcrime.securesms.database.MediaDatabase.MediaRecord; @@ -352,21 +354,15 @@ public class MediaPreviewActivity extends PassphraseRequiredActionBarActivity im saveTask.executeOnExecutor( AsyncTask.THREAD_POOL_EXECUTOR, new Attachment(mediaItem.uri, mediaItem.type, saveDate, null)); - // Sending a Data extraction notification (for incoming attachments only) - if(!mediaItem.outgoing) { - //TODO uncomment line below when Data extraction will be activated - //sendMediaSavedNotificationIfNeeded(); + if (!mediaItem.outgoing) { + sendMediaSavedNotificationIfNeeded(); } }) .execute(); }); } - /** - * Send a MediaSaved notification to the recipient - */ private void sendMediaSavedNotificationIfNeeded() { - // we don't send media saved notification for groups if (conversationRecipient.isGroupRecipient()) return; DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); MessageSender.send(message, conversationRecipient.getAddress()); diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java index 425160b45d..f8162aae0a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/ConversationFragment.java @@ -741,10 +741,8 @@ public class ConversationFragment extends Fragment if (!Util.isEmpty(attachments)) { SaveAttachmentTask saveTask = new SaveAttachmentTask(getActivity()); saveTask.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR, attachments.toArray(new SaveAttachmentTask.Attachment[0])); - // Sending a Data extraction notification (for incoming attachments only) - if(!message.isOutgoing()) { - //TODO uncomment line below when Data extraction will be activated - //sendMediaSavedNotificationIfNeeded(); + if (!message.isOutgoing()) { + sendMediaSavedNotificationIfNeeded(); } return; } @@ -758,11 +756,7 @@ public class ConversationFragment extends Fragment }); } - /** - * Send a MediaSaved notification to the recipient - */ private void sendMediaSavedNotificationIfNeeded() { - // we don't send media saved notification for groups if (recipient.isGroupRecipient()) return; DataExtractionNotification message = new DataExtractionNotification(new DataExtractionNotification.Kind.MediaSaved(System.currentTimeMillis())); MessageSender.send(message, recipient.getAddress()); From 3932d2fddddba67de07caf0445e433bab0e6404c Mon Sep 17 00:00:00 2001 From: Kee Jefferys Date: Tue, 1 Jun 2021 17:16:34 +1000 Subject: [PATCH 32/35] Create CODE_OF_CONDUCT.md --- CODE_OF_CONDUCT.md | 81 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 81 insertions(+) create mode 100644 CODE_OF_CONDUCT.md diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..b9cde981c4 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,81 @@ +# Contributor Covenant Code of Conduct + +## Our Pledge + +We as members, contributors, and leaders pledge to make participation in our +community a harassment-free experience for everyone. We pledge to act and interact in ways that contribute to an open, welcoming, +diverse, inclusive, and healthy community. + +## Our Standards + +Examples of behavior that contributes to a positive environment for our +community include: + +* Demonstrating empathy and kindness toward other people +* Being respectful of differing opinions, viewpoints, and experiences +* Giving and gracefully accepting constructive feedback +* Accepting responsibility and apologizing to those affected by our mistakes, + and learning from the experience +* Focusing on what is best not just for us as individuals, but for the + overall community + +Examples of unacceptable behavior include: + +* The use of sexualized language or imagery, and sexual attention or + advances of any kind +* Trolling, insulting or derogatory comments, and personal or political attacks +* Public or private harassment +* Publishing others' private information, such as a physical or email + address, without their explicit permission +* Other conduct which could reasonably be considered inappropriate in a + professional setting + +## Enforcement Responsibilities + +Community leaders are responsible for clarifying and enforcing our standards of +acceptable behavior and will take appropriate and fair corrective action in +response to any behavior that they deem inappropriate, threatening, offensive, +or harmful. + +Community leaders have the right and responsibility to remove, edit, or reject +comments, commits, code, wiki edits, issues, and other contributions that are +not aligned to this Code of Conduct, and will communicate reasons for moderation +decisions when appropriate. + +## Scope + +This Code of Conduct applies within all community spaces, and also applies when +an individual is officially representing the community in public spaces. +Examples of representing our community include using an official e-mail address, +posting via an official social media account, or acting as an appointed +representative at an online or offline event. + +## Enforcement + +Instances of abusive, harassing, or otherwise unacceptable behavior may be +reported to the community leaders responsible for enforcement at +team@oxen.io. +All complaints will be reviewed and investigated promptly and fairly. + +All community leaders are obligated to respect the privacy and security of the +reporter of any incident. + +## Enforcement Guidelines + +Community leaders will follow these Community Impact Guidelines in determining +the consequences for any action they deem in violation of this Code of Conduct: + +## Attribution + +This Code of Conduct is adapted from the [Contributor Covenant][homepage], +version 2.0, available at +https://www.contributor-covenant.org/version/2/0/code_of_conduct.html. + +Community Impact Guidelines were inspired by [Mozilla's code of conduct +enforcement ladder](https://github.com/mozilla/diversity). + +[homepage]: https://www.contributor-covenant.org + +For answers to common questions about this code of conduct, see the FAQ at +https://www.contributor-covenant.org/faq. Translations are available at +https://www.contributor-covenant.org/translations. From c8e31742884f897c0eb44c65460c2841f2e86924 Mon Sep 17 00:00:00 2001 From: Niels Andriesse <9340958+nielsandriesse@users.noreply.github.com> Date: Wed, 2 Jun 2021 09:18:34 +1000 Subject: [PATCH 33/35] Update ISSUE_TEMPLATE --- .github/ISSUE_TEMPLATE.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md index 0466db7e71..7c17252c2d 100644 --- a/.github/ISSUE_TEMPLATE.md +++ b/.github/ISSUE_TEMPLATE.md @@ -3,12 +3,11 @@ You can also preview your report before submitting it. You may remove sections t Before we begin, please note that this tracker is only for issues. It is not for questions, comments, or feature requests. - -If you are looking for support, please file an issue or: -or email team@loki.network +If you are looking for support, please file an issue or email team@oxen.io. Let's begin with a checklist: Replace the empty checkboxes [ ] below with checked ones [x] accordingly. --> +- [ ] I have read and agree to adhere to the [Code of Conduct](https://github.com/oxen-io/session-android/blob/master/CODE_OF_CONDUCT.md). - [ ] I have searched open and closed issues for duplicates - [ ] I am submitting a bug report for existing functionality that does not work as intended - [ ] This isn't a feature request or a discussion topic @@ -34,7 +33,6 @@ Describe here what should happen after you run the steps above (i.e. what would ### Screenshots - ### Device info From e117a3d2a2240f2441b9fdd3ac45c7862ca00fc6 Mon Sep 17 00:00:00 2001 From: Niels Andriesse <9340958+nielsandriesse@users.noreply.github.com> Date: Wed, 2 Jun 2021 09:19:30 +1000 Subject: [PATCH 34/35] Update bug_report --- .github/ISSUE_TEMPLATE/bug_report.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md index efa0e2bcbb..74bbafd0f6 100644 --- a/.github/ISSUE_TEMPLATE/bug_report.md +++ b/.github/ISSUE_TEMPLATE/bug_report.md @@ -7,6 +7,10 @@ assignees: '' --- +**Code of conduct** + +- [ ] I have read and agree to adhere to the [Code of Conduct](https://github.com/oxen-io/session-android/blob/master/CODE_OF_CONDUCT.md). + **Describe the bug** A clear and concise description of what the bug is. From 86ccc3767eb8b8151647777ee101e8c29a72ab63 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 2 Jun 2021 09:53:02 +1000 Subject: [PATCH 35/35] Update translations --- app/src/main/res/values-ar/strings.xml | 755 +++++++++++++++++++++ app/src/main/res/values-bg/strings.xml | 700 +++++++++++++++++++ app/src/main/res/values-ca/strings.xml | 352 +++++----- app/src/main/res/values-da/strings.xml | 86 +-- app/src/main/res/values-el/strings.xml | 10 +- app/src/main/res/values-es/strings.xml | 146 ++-- app/src/main/res/values-fa/strings.xml | 204 +++--- app/src/main/res/values-hu/strings.xml | 154 ++--- app/src/main/res/values-id/strings.xml | 30 +- app/src/main/res/values-it/strings.xml | 4 +- app/src/main/res/values-ru/strings.xml | 4 +- app/src/main/res/values-sk/strings.xml | 24 +- app/src/main/res/values-sv-rSE/strings.xml | 10 +- app/src/main/res/values-sw/strings.xml | 699 +++++++++++++++++++ 14 files changed, 2666 insertions(+), 512 deletions(-) create mode 100644 app/src/main/res/values-ar/strings.xml create mode 100644 app/src/main/res/values-bg/strings.xml create mode 100644 app/src/main/res/values-sw/strings.xml diff --git a/app/src/main/res/values-ar/strings.xml b/app/src/main/res/values-ar/strings.xml new file mode 100644 index 0000000000..82f9a660ef --- /dev/null +++ b/app/src/main/res/values-ar/strings.xml @@ -0,0 +1,755 @@ + + + جلسة + نعم + لا + حذف + حظر + الرجاء الانتظار... + حفظ + ملاحظة شخصية + الإصدار %s + + رسالة جديدة + + \+%d + + + %d رسالة في كل محادثة + %d رسالة في كل محادثة + %d رسائل في كل محادثة + %d رسائل في كل محادثة + %d + %d رسائل في كل محادثة + + احذف جميع الرسائل القديمة الآن؟ + + تقليم كل المحادثات إلى أحدث %d رسالة الآن. + سوف يتم تقليم كل المحادثات إلى أحدث رسالة. + سوف يتم تقليم كل المحادثات إلى أحدث %d رسالة الآن. + سوف يتم تقليم كل المحادثات إلى أحدث %d رسالة الآن. + سوف يتم تقليم كل المحادثات إلى أحدث %d رسالة الآن. + سوف يتم تقليم كل المحادثات إلى أحدث %d رسالة الآن. + + حذف + يعمل + مغلق + + (صورة) + (صوت) + (فيديو) + (رَدّ) + + لم يتم العثور على تطبيق لاختيار ملف. + يحتاج Session إلى إذن سعة التخزين من أجل إضافة الصور والمرفقات ولكن تم إيقاف الإذن على نحو دائم، رجاء زيارة إعدادات التطبيق ثم \"الأذونات\"، ثم تفعيل \"سعة التخزين\". + يحتاج Session إلى إذن جهات الاتصال من أجل إرفاق بيانات جهة اتصال ولكن تم إيقاف الإذن على نحو دائم، رجاء زيارة إعدادات التطبيق ثم \"الأذونات\"، ثم تفعيل \"جهات الاتصال\". + يحتاج Session إلى إذن الكاميرا من أجل التقاط صور ولكن تم إيقاف الإذن على نحو دائم، رجاء زيارة إعدادات التطبيق ثم \"الأذونات\"، ثم تفعيل \"الكاميرا\". + + خطأ في تشغيل الصوت! + + اليوم + البارحة + هذا الأسبوع + هذا الشهر + + لم يتم العثور على متصفح ويب. + + مجموعات + + فشل الإرسال , اضغط للحصول على معلومات + تم استلام رسالة تبادل مفاتيح، أنقر للمتابعة. + %1$s تَرك المجموعة. + فشل الإرسال، انقر للرد الغير آمن + لم يتم العثور على تطبيق قادر على فتح الملف. + تم نسخ %s +   إقرأ المزيد +   تنزيل المزيد +   في الإنتظار + + إضافة مرفقات + أختر بيانات جهة الاتصال + عذراً، حصل خطأ أثناء رفع المرفق. + رسالة + إنشاء رسالة + كتم حتى %1$s + %1$d عضو + إرشادات المستخدمين + جهة اتصال غير صحيحة! + تمت الإضافة للشاشة الرئيسية + ترك المجموعة؟ + هل أنت متأكد من ترك المجموعة؟ + خطأ أثناء مغادرة المجموعة + إلغاء حظر جهة الاتصال؟ + سوف تتمكن مرة أخرى من استقبال الرسائل والمكالمات من هذا المستخدم. + إلغاء الحظر + حجم المرفق تجاوز الحد المسموح بهذا النوع من الرسائل. + تعذر تسجيل الصوت! + لا يوجد تطبيق على الجهاز لمعالجة هذا الرابط. + إضافة أعضاء + انضم %s + هل انت متأكد من انك تريد الإنضمام إلى المجموعة المفتوحة %s؟ + لإرسال رسالة صوتية, برجاء السماح لSession بالوصول إلي المايكروفون + يحتاج Session إلى إذن الميكروفون من أجل الرسائل الصوتية ولكن تم إيقاف الإذن على نحو دائم، رجاء زيارة إعدادات التطبيق ثم \"الأذونات\"، ثم تفعيل \"الميكروفون\". + لإلتقاط صور و فيديوهات, برجاء السماح لSession بالوصول إلي الكاميرا. + يحتاج Session إلى إذن الكاميرا من أجل التقاط صور وفيديو ولكن تم إيقاف الإذن على نحو دائم، رجاء زيارة إعدادات التطبيق ثم \"الأذونات\"، ثم تفعيل \"الكاميرا\". + يحتاج Session إلي إذن الكاميرا لالتقاط صور أو تسجيل فيديو + %1$s%2$s + %1$d من %2$d + لا توجد نتائج + + + رسالة %d غير مقرؤة + رسالة %d غير مقرؤة + رسالتين %d غير مقرؤة + %dرسائل غير مقرؤة + %dرسائل غير مقرؤة + %dرسائل غير مقرؤة + + + + حذف المختار؟ + حذف الرسالة المختارة؟ + حذف الرسائل المختارة؟ + حذف الرسائل المختارة؟ + حذف الرسائل المختارة؟ + حذف الرسائل المختارة؟ + + + سوف يتم حذف %1$d رسالة مختارة نهائيا. + هذه العملية سوف تؤدي إلى حذف نهائي للرسالة الختارة. + سوف يتم حذف %1$d رسالة مختارة نهائيا. + سوف يتم حذف %1$d رسالة مختارة نهائيا. + سوف يتم حذف %1$d رسالة مختارة نهائيا. + سوف يتم حذف %1$d رسالة مختارة نهائيا. + + حظر هذا المستخدم؟ + أحفظ في الذاكرة؟ + + حفظ %1$d وسيط في الذاكرة يسمح ﻷي تطبيق آخر على الهاتف من النفاذ له. \n\nاستمر؟ + حفظ الوسائط في الذاكرة يسمح ﻷي تطبيق آخر على الهاتف من النفاذ له. \n\nاستمر؟ + حفظ %1$d وسيط في الذاكرة يسمح ﻷي تطبيق آخر على الهاتف من النفاذ له. \n\nاستمر؟ + حفظ %1$d وسيط في الذاكرة يسمح ﻷي تطبيق آخر على الهاتف من النفاذ له. \n\nاستمر؟ + حفظ %1$d وسيط في الذاكرة يسمح ﻷي تطبيق آخر على الهاتف من النفاذ له. \n\nاستمر؟ + حفظ %1$d وسيط في الذاكرة يسمح ﻷي تطبيق آخر على الهاتف من النفاذ له. \n\nاستمر؟ + + + وقع خطأ أثناء حفظ المرفقات في مساحة التخزين! + وقع خطأ أثناء حفظ المرفق في مساحة التخزين! + وقع خطأ أثناء حفظ المرفقات في مساحة التخزين! + وقع خطأ أثناء حفظ المرفقات في مساحة التخزين! + وقع خطأ أثناء حفظ المرفقات في مساحة التخزين! + وقع خطأ أثناء حفظ المرفقات في مساحة التخزين! + + + جارٍ حفظ %1$d مرفق + جارٍ حفظ المرفق + جارٍ حفظ %1$d مرفق + جارٍ حفظ %1$d مرفق + جارٍ حفظ %1$d مرفق + جارٍ حفظ %1$d مرفق + + + جارٍ حفظ %1$d مرفق في الذاكرة... + جارٍ حفظ المرفق في الذاكرة... + جارٍ حفظ %1$d مرفق في الذاكرة... + جارٍ حفظ %1$d مرفق في الذاكرة... + جارٍ حفظ %1$d مرفق في الذاكرة... + جارٍ حفظ %1$d مرفق في الذاكرة... + + معلق... + بيانات (Session) + رسالة وسائط متعددة + رسالة نصية + جارٍ الحذف + حذف الرسائل جارٍ... + حظر + حظر المستخدم… + لم يتم العثور على الرسالة الأصلية + الرسالة الأصلية لم تعد متوفرة + + رسالة تبادل المفاتيح + + الصورة الشخصية + + استخدام التخصيص: %s + الاستخدام الافتراضي: %s + لا شيء + + الآن + %d دقيقة + اليوم + البارحة + + اليوم + + ملف مجهول + + خطأ أثناء جلب كامل دقة صورة GIF + + صور GIF + لصاقات + + الصورة الرمزية + + ألمس وأضغط لتسجيل رسالة صوتية. اسحب إصبعك للإرسال + + لا يمكن العثور على الرسالة + رسالة من %1$s + رسالتك + + الوسائط + + حذف المحادثات المختارة؟ + حذف المحادثات المختارة؟ + حذف المحادثات المختارة؟ + حذف المحادثات المختارة؟ + حذف الرسائل المختارة؟ + حذف الرسائل المختارة؟ + + + سوف يؤدي هذا إلى حذف الرسائل المحددة بشكل نهائي. + This will permanently delete the selected message. + This will permanently delete all %1$d selected messages. + This will permanently delete all %1$d selected messages. + This will permanently delete all %1$d selected messages. + سوف يؤدي هذا إلى حذف رسائل %1$d بشكل نهائي. + + جارٍ الحذف + حذف الرسائل جارٍ... + الوثائق + أختر الكل + جارٍ جمع المرفقات... + + رسالة وسائط متعددة + تنزيل رسالة الوسائط المتعددة + خطأ في تنزيل رسالة الوسائط المتعددة، انقر لاعادة المحاولة + + إرسال إلى %s + + إضافة تعليق... + تم إزالة عنصر لانه تجاوز الحجم المسموح + الكاميرا غير متوفرة. + رسالة إلى %s + + لا يمكنك مشاركة اكثر من %d عنصر. + لا يمكنك مشاركة اكثر من %d عنصر. + لا يمكنك مشاركة اكثر من %d عنصر. + لا يمكنك مشاركة اكثر من %d عنصر. + لا يمكنك مشاركة اكثر من %d عنصر. + لا يمكنك مشاركة اكثر من %d عنصر. + + + جميع الوسائط + + لقد استلمت رسالة مشفرة من إصدارة Session قديمة وتوقف دعمها. من فضلك اطلب من الراسل الترقية إلي أحدث إصدار وإعادة إرسال الرسالة. + لقد تركت المجموعة. + قمت بتحديث المحموعة. + %s قام بتحديث المحموعة. + + الرسائل المختفية + صلاحية رسائلك لن تنتهي. + الرسائل الصادرة والواردة في هذه المحادثة سوف تختفي بعد %s من رؤيتهم. + + أدخل العبارة السرية + + حظر جهة الاتصال؟ + لن تتلقى الرسائل أو المكالمات من هذا المستخدم مستقبلا. + حظر + إلغاء حظر جهة الاتصال؟ + سوف تتمكن مرة أخرى من استقبال الرسائل والمكالمات من هذا المستخدم. + رفع الحظر + + صورة + صوت + فيديو + + تم استلام رسالة تبادل مفاتيح تالفة. + تم استلام رسالة تبادل مفاتيح مع إصدارة بروتوكول غير صحيحة. + تم استلام رسالة برقم أمان جديد. انقر للمعالجة والعرض. + لقد قمت بإعادة ضبط تأمين المحادثة. + %s أعاد ضبط تأمين المحادثة. + رسالة مكررة. + + تم تحديث المحموعة + غادر المجموعة + إعادة ضبط جلسة آمنة. + مسودة: + مكالمات صادرة + مكالمات واردة + مكالمة فائتة + رسالة وسائط متعددة + %sمتواجد على Session ! + تم تعطيل الرسائل المخفية + ضبط توقيت إخفاء الرسائل إلى %s + قام %s بتصوير الشاشة. + قام %s بحفظ الوسائط. + تم تعديل رقم الأمان + تم تغيير رقم الأمان الخاص بك وبـ%s. + لقد قمت بتصديق + لقد قمت بإلغاء التحقق + هذه المحادثة فارغة + دعوة إلى مجموعة مفتوحة + + تحديث Session + إصدار جديد لـ Session متوفر، أنقر لتحديثه + + رسالة مشفرة رديئة + الرسالة مشفرة دون وجود قناة اتصال... + + رسالة مشفرة رديئة + الرسالة مشفرة دون وجود قناة اتصال... + + كتم الإشعارات + + المس للفتح. + Session غير مقفول + إقفِل Session + + أنت + غير مدعوم. + مسودة: + يحتاج Session إلى إذن سعة التخزين من أجل استخدام الذاكرة الخارجية ولكن تم إيقاف الإذن على نحو دائم، رجاء زيارة إعدادات التطبيق ثم \"الأذونات\"، ثم تفعيل \"سعة التخزين\". + لا يمكن الحفظ إلى الذاكرة الخارجية بدون أذونات + أَتريد حذف الرسالة ؟ + سوف يقوم هذا بحذف الرسالة كُليًا. + + %1$d رسائل جديدة في %2$d محادثات + الأحدث من: %1$s + رسالة مؤمنة + فشل توصيل الرسالة. + فشل توصيل الرسالة. + خطأ بالارسال + اعتبر جميع الرسائل مقروءة + اعتبرها مقروءة + رَدّ + رسائل Session المعلقة + لديك رسائل جلسة معلّقة، انقر للفتح و الإستلام + %1$s%2$s + متصل + + افتراضي + المُكالمات + إخفاقات + النُسخ الإحتياطية + حالة القفل + تحديثات التطبيق + أخرى + الرسائل + مجهول + + الردود السريعة غير متاحة عند قفل Session! + مشكلة في الإرسال! + + تم الإحتفاظ به في %s + تم الحفظ + + بحث + + إختصار غير صالح + + الجلسة + رسالة جديدة + + + %d Items + %d Item + %d Items + %d Items + %d عنصر + %d Items + + + خطأ في تشغيل الفيديو + + صوت + صوت + جهة اتصال + متصل + كاميرا + كاميرا + الموقع + موقع جغرافي + صورة GIF + صورة Gif + صورة أو فيديو + ملف + عرض الصور + ملف + تفعيل أو تثبيط درج المرفقات + + تحميل جهات الاتصال... + + أرسل + تكوين الرسالة + تبديل لوحة مفاتيح الرموز التعبيرية + المصغّرة للمرفق + تفعيل أو تثبيط سريع لدرج مرفقات الكاميرا + تسجيل وإرسال مرفق صوتي + قفل تسجيل الملاحظات الصوتية + تمكين سيقنـال للرسائل النصية + + إسحب للإلغاء + إلغاء + + رسالة وسائط متعددة + رسالة آمنة + + الإرسال فشل + في انتظار الموافقة + تم تسليم الرسالة + تم قراءة الرسالة + + صورة جهة الاتصال + + تشغيل + توقف + تنزيل + + إنضم + دعوة المجموعة المفتوحة + الرسائل المثبّتة + Community guidelines + Read + + صوت + فيديو + صورة + أنت + لم يتم العثور على الرسالة الأصلية + + النزول إلى الأسفل + + ابحث في صور GIF واللصاقات + + لا يوجد شيء + + مطالعة المحادثة بالكامل + جارٍ التحميل + + لا يوجد وسيط + + إعادة الإرسال + + حظر + + بعض المشاكل تتطلب انتباهك. + أرسلت + استلمت + الاختفاء + بواسطة + إلى: + من: + مع: + + إنشاء عبارة سرية + اخر جهات الاتصال + معاينة الوسائط + + استخدم الافتراضي + تخصيص + كتم لمدة ساعة + كتم لمدة ساعتين + كتم لمدة يوم + كتم لمدة سبعة أيام + كتم لمدة عام واحد + الإعدادات الافتراضية + مفعل + معطل + الاسم والرسالة + الاسم فقط + لا يوجد أسم أو رسالة + صور + صوت + فيديو + الوثائق + صغيرة + طبيعي + كبيرة + كبيرة جداً + افتراضي + عالي + الأقصى + + + %d ساعة + %d ساعة + %d ساعتين + %d ساعات + %d ساعة + %d ساعة + + + مفتاح الدخول يقوم بالإرسال + الضغط على مفتاح الدخول سيقوم بإرسال الرسالة + Send link previews + Previews are supported for Imgur, Instagram, Pinterest, Reddit, and YouTube links + تأمين الشاشة + منع لقطات الشاشة داخل التطبيق + الإشعارات + لون ضوء التنبيه LED + مجهول + نمط وميض LED + صوت + صامت + تكرار التنبيهات + أبدا + مرة واحدة + مرتان + ثلاث مرات + خمس مرات + عشر مرات + هزاز + أخضر + أحمر + أزرق + برتقالي + سماوي + وردي + أبيض + لا شيء + سريع + طبيعي + بطىء + حذف الرسائل القديمة بالمحادثة عند تجاوز أقصى طول محدد. + احذف الرسائل القديمة + حد طول المحادثة + تقليم كل المحادثات الآن + فحص وتقليم كل المحادثات وفق الحد الأقصى + افتراضي + وضع التستّر للوحة المفاتيح + قراءة تقارير الإستلام + لن يمكنك من استقبال مؤشر قراءة الرسائل من الآخرين إذا قمت بإلغاء مؤشر القراءة. + Typing indicators + If typing indicators are disabled, you won\'t be able to see typing indicators from others. + الطلب من لوحة المفاتيح تعطيل التعلم الذاتي + فاتح + داكن + تقليم الرسالة + استخدام الرموز التعبيرية بالنظام + تعطيل الرموز التعبيرية المدمجة في Session + دخول البرنامج + اتصال + دردشة + الرسائل + أصوات خلال الدردشة + اظهار + أولوية + + + + + رسالة جديدة إلى... + + تفاصيل الرسالة + نسخ النص + حذف الرسالة + Ban user + إعادة الإرسال + الرَدّ على الرسالة + + حفظ المرفق + + الرسائل المختفية + + صلاحية الرسائل + + إلغاء الكتم. + + كتم الإشعارات + + تدقيق المجموعة + أترك المجموعة + جميع الوسائط + Add to home screen + + توسيع الإشعار + + تسليم + محادثة + بث + + أحفظ + إعادة توجيه + جميع الوسائط + + لا توجد ملفات + + معاينة الوسائط + + جارٍ الحذف + جار حذف الرسائل القديمة... + تم حذف الرسائل القديمة بنجاح + + منح إحدى الأذونات مطلوب + استمر + ليس الآن + يتم حفظ النسخ الاحتياطية إلى سعة التخزين مشفرة بالعبارة السرية التالية، ومن الضروري حفظها من أجل استعادة قاعدة البيانات. + قمت بتدوين العبارة السرية، ولا يمكنني استعادة قاعدة البيانات بدونها. + تخطى + Cannot import backups from newer versions of Session + العبارة السرية غير صحيحة + أتريد تشغيل النُّسخ الإحتاطية المحلية ؟ + تمكين النُسخ الإحتياطية + رجاء الإقرار بفهم الخاصية عبر اختيار علامة التأكيد. + هل تود حذف النسخ الإحتياطية ؟ + أتريد تعطيل و حذف كافة النُّسخ الإحتياطية المحلية ؟ + حذف النسخ الإحتياطية + نسخ إلى الحافظة + جارٍ إنشاء نسخة احتياطية... + %dرسالة إلى الآن + أبدا + قفل الشاشة + منع النفاذ إلى Session عبر تعيين قفل الشاشة أو بصمة الإصبع + نفاذ مهلة قفل الشاشة + لا شيء + + Copy public key + + Continue + Copy + Invalid URL + Copied to clipboard + Next + Share + Invalid Session ID + Cancel + Your Session ID + Your Session begins here... + Create Session ID + Continue Your Session + What\'s Session? + It\'s a decentralized, encrypted messaging app + So it doesn\'t collect my personal information or my conversation metadata? How does it work? + Using a combination of advanced anonymous routing and end-to-end encryption technologies. + Friends don\'t let friends use compromised messengers. You\'re welcome. + Say hello to your Session ID + Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design. + Restore your account + Enter the recovery phrase that was given to you when you signed up to restore your account. + Enter your recovery phrase + Pick your display name + This will be your name when you use Session. It can be your real name, an alias, or anything else you like. + Enter a display name + Please pick a display name + Please pick a shorter display name + Recommended + Please Pick an Option + You don\'t have any contacts yet + Start a Session + Are you sure you want to leave this group? + "Couldn't leave group" + Are you sure you want to delete this conversation? + Conversation deleted + Your Recovery Phrase + Meet your recovery phrase + Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don\'t give it to anyone. + انقر مطولاً للكشف + انت على وشك الإنتهاء! ٨٠٪ + أمّن حسابك بحفظ كلمات إسترجاع الحساب + انقر بإستمرار على الكلمات المغطاة للكشف عن كلمات إسترداد حسابك، ثم قم بتخزينها بأمان للحفاظ على معرّف الجلسة الخاص بك. + تأكد من الإحتفاظ بكلمات الإسترجاع الخاصة بك في مكان آمن + مسار + سيشن يخفي عنوان الآيبي الخاص بك عن طريق تمرير رسائلك عبر عدة سيرفرات في شبكة سيشن الغير مركزية. هذه الدول اللتي يتم تمرير اتصالك فيها حالياً: + أنت + عقدة الدخول + عقدة الخدمة + الوجهة + لمعرفة المزيد + جلسة جديدة + أدخِل معرّف الجلسة + فحص كود QR + إمسح كود QR المستخدم لبدء جلسة معه. يمكن الحصول على اكواد الـQR بالضغط على ايقونة كود الـQR في إعدادات الحساب. + ادخِل معرّف الجلسة للمستخدم + يمكن للمستخدمين مشاركة معرّف سيشن الخاص بهم بالذهاب إلى إعدادات حسابهم و الضغط على \"مشاركة معرف سيشن\"، او عبر مشاركة كود QR الخاص بهم. + تحتاج الجلسة إلى الوصول إلى الكاميرا لمسح رموز QR + منح صلاحية الكاميرا + مجموعة مغلقة جديدة + أدخل إسم المجموعة + لا يوجد لديك جهات إتصال حالياً + بدء جلسة + الرجاء إدخال إسم للمجموعة + الرجاء إدخال إسم مجموعة أقصر + الرجاء إختيار 1 عضو على الأقل + المجموعة المغلقة لا يمكن ان تحتوي على أكثر من ١٠٠ مستخدم + الإنضمام إلى مجموعة مفتوحة + تعذر الإنضمام إلى المجموعة + فتح رابط المجموعة + فحص كود QR + امسح كود QR المجموعة اللتي تريد الإنضمام إليها + ادخل رابط المجموعة المفتوحة + الإعدادات + ادخِل إسم العرض + الرجاء إختيار إسم العرض + الرجاء إختيار اسم عرض أقصر + الخصوصية + الإشعارات + المحادثات + الأجهزة + إرسال دعوة + عبارة الإسترداد + مسح البيانات + ساعدنا في ترجمة سيشن + الإشعارات + طريقة الإشعارات + محتوى الإشعارات + الخصوصية + Chats + Notification Strategy + Change name + Unlink device + Your Recovery Phrase + This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device. + Clear All Data + This will permanently delete your messages, sessions, and contacts. + QR Code + View My QR Code + Scan QR Code + Scan someone\'s QR code to start a conversation with them + Scan Me + This is your QR code. Other users can scan it to start a session with you. + Share QR Code + Contacts + Closed Groups + Open Groups + You don\'t have any contacts yet + + Apply + Done + Edit Group + Enter a new group name + Members + Add members + Group name can\'t be empty + Please enter a shorter group name + Groups must have at least 1 group member + Remove user from group + Select Contacts + Secure session reset done + Theme + Day + Night + System default + Copy Session ID + Attachment + Voice Message + Details + Failed to activate backups. Please try again or contact support. + Restore backup + Select a file + Select a backup file and enter the passphrase it was created with. + 30-digit passphrase + This is taking a while, would you like to skip? + Link a Device + Or join one of these… + Message Notifications + There are two ways Session can notify you of new messages. + Fast Mode + الوضع البطيء + سوف يتم إشعارك بالرسائل بشكل موثوق و فوري بإستخدام خوادم جوجل للإشعارات. + سيقوم سيشن بالتحقق من وجود رسائل جديدة بشكل دوري في الخلفية. + عبارة الإسترداد + إن سيشن مُقفَل + انقر للفتح + Enter a nickname + Invalid public key + diff --git a/app/src/main/res/values-bg/strings.xml b/app/src/main/res/values-bg/strings.xml new file mode 100644 index 0000000000..de816a2c83 --- /dev/null +++ b/app/src/main/res/values-bg/strings.xml @@ -0,0 +1,700 @@ + + + Session + Да + Не + Изтрий + Ban + Моля, изчакайте... + Запази + Бележка за Мен + Version %s + + Ново съобщение + + \+%d + + + %dсъобщение на разговор + %d съобщения на разговор + + Изтрий всички стари съобщения сега? + + Това ще скъси всички разговори до най-новoто съобщение. + Това ще скъси всички разговори до %dте най-нови съобщения. + + Изтрий + Активирано + Деактивирано + + (изображение) + (аудио) + (видео) + (отговори) + + Неуспешно откриване на папка за избор на файл. + Session се нуждае от достъп до вградения диск, за да може да прикачва снимки, видеота или аудио, но той му е отказан. Моля, отидете в настройки в менюто и изберете \"Разрешения\" и \"Дискове\". + Session се нуждае от достъп до контактите Ви, за да може да прикачва информация за тях, но той му е отказан. Моля, отидете на настройки в менюто и изберете \"Разрешения\" и \"Контакти\". + Session се нуждае от достъп до камерта Ви, за да може да прави снимки, но той му е отказан. Моля, отидете на настройки в менюто и изберете \"Разрешения\" и \"Камера\". + + Грешка при възпроизвеждане на аудио! + + Днес + Вчера + Тази седмица + Този месец + + Не е открит уеб браузър. + + Групи + + Неуспешно изпращане, натиснете за повече информация + Получи се съобщение за обмяна на ключове, натисни, за да продължиш. + %1$s напусна групата. + Неуспешно изпращане, натиснете за изпращане по несигурен начин + Неуспешно откриване на приложение за отваряне на този файл. + %s e копирано + Прочети + Изтегли Повече +  в Очакване + + Прикачване на файл + Посочи информация за контакта + За съжаление, настъпи грешка при прикачването. + Message + Compose + Muted until %1$s + %1$d members + Community Guidelines + Невалиден получател! + Добавено на работния плот + Напускане на групата? + Сигурен ли си, че искаш да напуснеш тази група? + Грешка при напускане на групата + Отблокирване на този контакт? + Отново ще може да получавате съобщения и обаждания от този контакт. + Отблокирване + Размерът на прикачения файл надминава допустимия лимит за типа съобщение, който изпращате. + Не може да бъде записано аудио! + Нямате инсталирано приложение, което да може да използва тази препратка. + Add members + Join %s + Are you sure you want to join the %s open group? + За да изпратите аудио съобщение, разрешете достъпа на Session до микрофона. + Session се нуждае от достъп до микрофона Ви, за да може да изпраща аудио съобщения, но той му е отказан. Моля, отидете в настройки в менюто и изберете \"Разрешения\" и \"Микрофон\". + За прави снимки и видеота, Session се нуждае от достъп до камерта Ви. + Session се нуждае от достъп до камерта Ви, за да може да прави снимки или видеота, но той му е отказан. Моля, отидете в настройки в менюто и изберете \"Разрешения\" и \"Камера\". + Session се нуждае от достъп до камерата, за да прави снимки и видеота + %1$s %2$s + %1$d от %2$d + Няма резултати + + + %d непрочетено съобщение + %d непрочетени съобщения + + + + Изтриване на избраното съобщение? + Изтриване на избраните съобщения? + + + Това ще изтрие невъзращаемо избраното съобщение. + Това ще изтрие невъзвращаемо всичките %1$d избрани съобщения. + + Ban this user? + Запази в хранилището? + + Запазването на този медиен файл ще даде достъп до него на всички други приложения.\n\nПродължи? + Запазването на всички %1$d медийни файла ще даде достъп до тях на всички други приложения.\n\nПродължи? + + + Грешка при запазването на прикаченият файл! + Грешка при запазването на прикачените файлове! + + + Запазване на прикачения файл + Запазване на %1$d прикачени файла + + + Запазване на прикачения файл в хранилището... + Запазване на %1$d прикачени файла в хранилището... + + Предстоящ... + Данни (Session) + MMS + SMS + Изтриване + Изтриване на съобщения... + Banning + Banning user… + Оригиналното съобщение не е открито + Оригиналното съобщение вече не е налично + + Съобщение за Изменя на ключожете + + Снимка на профила + + Използване на специален: %s + Използване на подразбиращ се: %s + Нищо + + Току що + %d мин + Днес + Вчера + + Днес + + Непознат файл + + Грешка при вземането на GIF в пълна резолюция + + GIF-ове + Стикери + + Снимка + + Натиснете и задръжте, за да запишете гласово съобщение, отпуснете, за да го изпратите + + Съобщението не е намерено + Съобщение от %1$s + Вашето съобщение + + Медия + + Изтриване на избраното съобщение? + Изтриване на избраните съобщения? + + + Това ще изтрие невъзвращаемо избраното съобщение. + Това ще изтрие невъзвращаемо всичките %1$d избрани съобщения. + + Изтриване + Изтриване на съобщения... + Документи + Избери всичко + Събиране на прикачени файлове... + + Мултимедийно съобщение + Изтегляне на MMS съобщение + Грешка при изтегляне на MMS съобщение, натиснете за да опитате повторно + + Изпрати на %s + + Добави надпис... + Един елемент бе премахнат, защото надхвърля ограничението за размер + Не е открита камера. + Изпрати на %s + + Не можете да споделяте повече от %d елемент. + Не можете да споделяте повече от %d елементи. + + + Всички прикачените файлове + + Получихте съобщение криптирано със стара версия на Session, която вече не се поддържа. Моля, помолете изпращача да обнови версията си и да препрати съобщението. + Напуснахте групата. + Обновихте групата. + %s обнови групата. + + Изчезващи съобщения + Съобщенията няма да изчезват. + Изпратени и получени съобщения в този разговор ще изчезнат %s след изпращането им. + + Въведете паролата + + Блокиране на този контакт? + Повече няма да получавате съобщения и обаждания от този контакт. + Блокиране + Отблокиране на този контакт? + Отново ще може да получавате съобщения и обаждания от този контакт. + Отблокиране + + Изображение + Аудио + Видео + + Съобщението за обмяна на ключ +е объркано! + Съобщението за обмяна на ключа е с грешна версия на протокола. + Получихте съобщение с нови числа за сигурност. Натиснете, за да ги видите и обработите. + Вие рестартирахте сигурната сесия. + %s рестартира сигурната сесия. + Дублирай съобщението. + + Групата е обновена + Напусна групата + Започване на нова сигурна сесия. + Чернова: + Вие се обадихте + Ви се обади + Пропуснато обаждане + Медийно съобщение + %s е в Session! + Изчезващите съобщения са деактивирани + Времето за изчезване на съобщенията е %s + %s took a screenshot. + Media saved by %s. + Числата за сигурност се промениха + Числата Ви за сигурност с %s са променени. + Отбелязахте като потвърдено + Отбелязахте като непотвърдено + This conversation is empty + Open group invitation + + Обновление на Session + Има нова версия на Session, натиснете, за да обновите. + + Грешно криптирано съобщение + Съобщението е криптирано за несъществуаща сесия + + Грешно криптирано MMS съобщение + MMS съобщението е криптирано за несъществуваща сесия + + Тих режим за известия + + Натиснете за отворяне. + Session е отключен + Заключване на Session + + Ти + Неподдържан медиен формат. + Чернова + Session се нуждае от достъп до вградения диск, за да може да запазва на него, но той му е отказан. Моля, отидете в настройки в менюто и изберете \"Разрешения\" и \"Дискове\". + Неуспешно запазване на външен диск без нуждното разрешение за достъп + Изтриване на съобщението? + Това ще изтрие невъзвращаемо текущото съобщение. + + %1$d нови съобщения в %2$d чата + Най-скорошно от: %1$s + Заключено съобщение + Изпращането на съобщението неуспешно. + Неуспешно изпращането на съобщението. + Грешка при изпращането на съобщението + Всички са прочетени + Прочетено + Отговори + Непрочетени Session съобщения + Имате непрочетени Session съобщения, натиснете, за да ги отворите и прегледате + %1$s %2$s + Контакт + + По подразбиране + Обаждания + Грешки + Архиви + Статус на заключването + Обновления на приложението + Друг + Съобщения + Непознат + + Бърз отговор не е възможен, когато Session е заключен! + Проблем при изпрашане на съобщение! + + Запзване в %s + Запазено + + Търсене + + Невалидена препратка + + Session + Ново съобщение + + + %d нещо + %d неща + + + Грешка при пускане на видео + + Аудио + Аудио + Контакт + Контакт + Камера + Камера + Местоположение + Местоположение + GIF + Gif + Изображение или видео + Файл + Галерия + Файл + Затваряне/отваряне на чекмеджето с прикачени файлове + + Зареждане на контакти... + + Изпращане + Създаване на съобщение + Смени emoji клавиатурата + Изображение на прикачен файл + Затворяне/отворяне на чекмеджето за прикчаване на файл от камера + Записване и изпращане прикачено аудио + Lock recording of audio attachment + Активиране на Session за SMS-и + + Плъзнете за отказ + Отказ + + Медийно съобщение + Подсигурено съобщение + + Неуспешно Изпращане + Очаква Потвърждение + Доставено + Съобщението е прочетено + + Снимка на контакта + + Пускане + Пауза + Изтегляне + + Join + Open group invitation + Pinned message + Community guidelines + Read + + Аудио + Видео + Снимка + Ти + Оригиналното съобщение не е открито + + Превъртане надолу + + Търсене сред GIF-ове и стикери + + Няма нищо открито + + Разгледай целия разговор + Зареждане + + Без медия + + ПОВТОРНО ИЗПРАЩАНЕ + + Блокиране + + Няколко проблема изискват вашето внимание. + Изпратен + Получен + Изчезва + През + До: + От: + С: + + Създай парола + Избери контакти + Преглед на медията + + Използване на подразбиращ се + Използване на специален + Тих режим за 1 час + Тих режим за 2 часа + Тих режим за 1 ден + Тих режим за 7 дена + Тих режим за 1 година + Настройки по подразбиране + Разрешено + Деактивирано + Име и съобщение + Само име + Няма име или съобщения + Изображения + Аудио + Видео + Документи + Малък + Нормален + Голям + Много голям + По подразбиране + Високо + Максимално + + + %d час + %d часа + + + Клавишът Enter се използва за изпращане + Натискане на клавиша Enter ще изпрати текстово съобщене + Изпращане на визуализации на връзката + Визуализации са поддържани за линкове към Imgur, Instagram, Pinterest, Reddit, и YouTube. + Сигурност на екрана + Забраняване на автоматично копиране на екрана в списка с често използвани програми и в самата програма. + Известия + LED цвят + Непознат + LED известяване + Мелодия + Тих + Повтарящи се аларми + Никога + Само веднъж + Два пъти + Три пъти + Пет пъти + Десет пъти + Вибрация + Зелен + Червен + Син + Оранжево + Циан + Маджента + Бял + Нищо + Бързо + Нормално + Бавно + Автоматично изтрий най-старите съобщения, когато разговорът достигне определена дължина. + Изтрий старите съобщения + Граница за дължина на разговора + Скъси всички разговори сега + Сканирай всички разговори и приложи ограничението за дължина на разговори + По подразбиране + Тайна клавиатура + Потвърждения за прочитане + Ако Вашите потвърждения за прочитане са деактивирани няма да можете да видите потвържденията за прочитане от други. + Индикатори за писане + Ако индикаторите за писане са декативирани, няма да можете да видите индикаторите на другите хора. + Поискване на клавиатурата да деактивира персонализрано научаване + Светла + Тъмна + Съкращаване на съобщенията + Използване на вградените emoji-та + Деактивирай вградена в Session подръжка на emoji-та + Достъп до приложението + Комуникация + Чатове + Съобщения + Звуци в чатове + Показване + Приоритет + + + + + Ново съобщение до... + + Информация за съобщението + Копирай текста + Изтрий съобщението + Ban user + Повторно изпращане + Отговор на съобщение + + Запази прикачения файл + + Изчезващи съобщения + + Съобщения, които изчеват + + Изключи тих режим + + Тих режим за известия + + Промяна на групата + Напускане на групата + Всички прикачените файлове + Добавяне на работния плот + + Разшири диалога + + Доставка + Разговор + Предаване + + Запази + Препращане + Всички прикачени файлове + + Няма документи + + Преглед на медията + + Изтриване + Изтриване на стари съобщения... + Старите съобщения бяха успешно изтрити + + Нужда от разрешение за достъп + Продължи + Не сега + Архивите ще бъдат запазени на външно хранилище и криптирани с паролата задена по-долу. Трябва да използвате същата парола за въстановяване от архива. + Записах тази парола. Без нея, няма да мога да възстановя архива. + Пропусни + Не е възможно внасянето на складирани съобщения от по-нов версия на Session. + Невалидна парола за архива + Активиране на локални архиви? + Активиране на архиви + Моля, потвърдете съгласието си. + Изтриване на архивите? + Деактивиране и изтриване на всички локални архиви? + Изтриване на архиви + Копирано + Създаване на архив... + %d съобщения до тук + Никога + Заключен екран + Заключване на достъпа до Session с вградените в Андроид заключен екран или отпечатък + Време за активирнае при неактивност + Нищо + + Copy public key + + Continue + Copy + Invalid URL + Copied to clipboard + Next + Share + Invalid Session ID + Cancel + Your Session ID + Your Session begins here... + Create Session ID + Continue Your Session + What\'s Session? + It\'s a decentralized, encrypted messaging app + So it doesn\'t collect my personal information or my conversation metadata? How does it work? + Using a combination of advanced anonymous routing and end-to-end encryption technologies. + Friends don\'t let friends use compromised messengers. You\'re welcome. + Say hello to your Session ID + Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design. + Restore your account + Enter the recovery phrase that was given to you when you signed up to restore your account. + Enter your recovery phrase + Pick your display name + This will be your name when you use Session. It can be your real name, an alias, or anything else you like. + Enter a display name + Please pick a display name + Please pick a shorter display name + Recommended + Please Pick an Option + You don\'t have any contacts yet + Start a Session + Are you sure you want to leave this group? + "Couldn't leave group" + Are you sure you want to delete this conversation? + Conversation deleted + Your Recovery Phrase + Meet your recovery phrase + Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don\'t give it to anyone. + Hold to reveal + You\'re almost finished! 80% + Secure your account by saving your recovery phrase + Tap and hold the redacted words to reveal your recovery phrase, then store it safely to secure your Session ID. + Make sure to store your recovery phrase in a safe place + Path + Session hides your IP by bouncing your messages through several Service Nodes in Session\'s decentralized network. These are the countries your connection is currently being bounced through: + You + Entry Node + Service Node + Destination + Learn More + New Session + Enter Session ID + Scan QR Code + Scan a user\'s QR code to start a session. QR codes can be found by tapping the QR code icon in account settings. + Enter Session ID of recipient + Users can share their Session ID by going into their account settings and tapping \"Share Session ID\", or by sharing their QR code. + Session needs camera access to scan QR codes + Grant Camera Access + New Closed Group + Enter a group name + You don\'t have any contacts yet + Start a Session + Please enter a group name + Please enter a shorter group name + Please pick at least 1 group member + A closed group cannot have more than 100 members + Join Open Group + Couldn\'t join group + Open Group URL + Scan QR Code + Scan the QR code of the open group you\'d like to join + Enter an open group URL + Settings + Enter a display name + Please pick a display name + Please pick a shorter display name + Privacy + Notifications + Chats + Devices + Invite + Recovery Phrase + Clear Data + Help us Translate Session + Notifications + Notification Style + Notification Content + Privacy + Chats + Notification Strategy + Change name + Unlink device + Your Recovery Phrase + This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device. + Clear All Data + This will permanently delete your messages, sessions, and contacts. + QR Code + View My QR Code + Scan QR Code + Scan someone\'s QR code to start a conversation with them + Scan Me + This is your QR code. Other users can scan it to start a session with you. + Share QR Code + Contacts + Closed Groups + Open Groups + You don\'t have any contacts yet + + Apply + Done + Edit Group + Enter a new group name + Members + Add members + Group name can\'t be empty + Please enter a shorter group name + Groups must have at least 1 group member + Remove user from group + Select Contacts + Secure session reset done + Theme + Day + Night + System default + Copy Session ID + Attachment + Voice Message + Details + Failed to activate backups. Please try again or contact support. + Restore backup + Select a file + Select a backup file and enter the passphrase it was created with. + 30-digit passphrase + This is taking a while, would you like to skip? + Link a Device + Or join one of these… + Message Notifications + There are two ways Session can notify you of new messages. + Fast Mode + Slow Mode + You’ll be notified of new messages reliably and immediately using Google’s notification servers. + Session will occasionally check for new messages in the background. + Recovery Phrase + Session is Locked + Tap to Unlock + Enter a nickname + Invalid public key + diff --git a/app/src/main/res/values-ca/strings.xml b/app/src/main/res/values-ca/strings.xml index 8461743256..dca7d0c440 100644 --- a/app/src/main/res/values-ca/strings.xml +++ b/app/src/main/res/values-ca/strings.xml @@ -1,14 +1,14 @@ - Session + Sessió No Suprimeix - Ban + Bloca Espereu, si us plau... Desa Notifica-m\'ho - Version %s + Versió %s Missatge nou @@ -48,7 +48,7 @@ Grups - Ha fallat l\'enviament. Toqueu per saber-ne més. + Ha fallat l\'enviament. Toqueu per a saber-ne més S\'ha rebut el missatge de l\'intercanvi de la clau, toqueu per a processar-lo. %1$s ha abandonat el grup. Ha fallat l\'enviament. Toqueu per al mode no segur. @@ -61,11 +61,11 @@ Afegeix un adjunt Seleccioneu informació del contacte S\'ha produït un error en afegir el fitxer adjunt. - Message - Compose - Muted until %1$s - %1$d members - Community Guidelines + Missatge + Escriu + Silenciat fins %1$s + %1$d membres + Directrius de la comunitat Destinatari no vàlid. S\'ha afegit a la pantalla d\'inici. Voleu abandonar el grup? @@ -77,9 +77,9 @@ El fitxer adjunt excedeix la mida màxima per a aquest tipus de missatges. No s\'ha pogut enregistrar l\'àudio. No hi ha cap aplicació que pugui obrir aquest enllaç. - Add members - Join %s - Are you sure you want to join the %s open group? + Afegeix membres + Uneix-t\'hi %s + Estàs segur que vols unir-te el grup obert %s? Per enviar missatges d\'àudio, permeteu que el Session tingui accés al micròfon. El Session necessita el permís del micròfon per tal d\'enviar missatges d\'àudio, però s\'ha denegat permanentment. Si us plau, continueu cap al menú de configuració de l\'aplicació, seleccioneu Permisos i habiliteu-hi el micròfon. Per captar fotografies i vídeos, permeteu que el Session tingui accés a la càmera. @@ -102,7 +102,7 @@ Se suprimirà de manera permanent el missatge seleccionat. Se suprimiran de manera permanent els %1$d missatges seleccionats. - Ban this user? + Vols blocar aquest usuari? Voleu desar-ho a l\'emmagatzematge? Desar aquest mitjà a emmagatzematge permetrà que qualsevol altra aplicació del dispositiu també hi pugui accedir.\n\nVoleu continuar? @@ -126,8 +126,8 @@ SMS Suprimint Suprimint missatges... - Banning - Banning user… + Blocant + Blocant usuari… No s\'ha trobat el missatge original. El missatge original ja no està disponible. @@ -199,7 +199,7 @@ %s ha actualitzat el grup. Missatges efímers - El missatges no expiraran. + El missatges no caducaran. Els missatges enviats i rebuts a aquesta conversa desapareixeran %s després que s\'hagin llegit. Introduïu la contrasenya @@ -234,14 +234,14 @@ d\'intercanvi de claus! %s és al Session! Missatges efímers inhabilitats El temps del missatge efímer s\'ha establit a %s - %s took a screenshot. - Media saved by %s. + %s ha fet una captura de pantalla. + Mèdia desada per %s. El número de seguretat ha canviat El número de seguretat amb %s ha canviat. Heu verificat Heu tret la verificació - This conversation is empty - Open group invitation + Aquesta conversa és buida + Obre un convit de grup Actualització del Session Hi ha disponible una versió nova del Session. Toqueu per actualitzar-lo @@ -281,7 +281,7 @@ d\'intercanvi de claus! Contacte Per defecte - Trucades + Telefonades Fallades Còpies de seguretat Estat del bloqueig @@ -300,7 +300,7 @@ d\'intercanvi de claus! Drecera no vàlida - Session + Sessió Missatge nou @@ -354,11 +354,11 @@ d\'intercanvi de claus! Pausa Baixa - Join - Open group invitation - Pinned message - Community guidelines - Read + Uneix-t\'hi + Obre convit de grup + Missatge fixat + Directrius de comunitat + Llegeix Àudio Vídeo @@ -466,7 +466,7 @@ d\'intercanvi de claus! Si les confirmacions de recepció estan inhabilitades, no podreu veure les dels altres. Indicadors de tecleig Si els indicadors de tecleig estan inhabilitats, no podreu veure els indicadors de tecleig dels altres. - Demana el teclat per inhabilitar l\'aprenentatge personalitzat + Demana el teclat per a inhabilitar l\'aprenentatge personalitzat Clar Fosc Escapçament de missatges @@ -488,7 +488,7 @@ d\'intercanvi de claus! Detalls del missatge Copia el text Suprimeix el missatge - Ban user + Bloca l\'usuari Torna a enviar el missatge Respon el missatge @@ -548,153 +548,153 @@ d\'intercanvi de claus! Temps d\'inactivitat per al bloqueig de pantalla Cap - Copy public key + Copia la clau pública - Continue - Copy - Invalid URL - Copied to clipboard - Next - Share - Invalid Session ID - Cancel - Your Session ID - Your Session begins here... - Create Session ID - Continue Your Session - What\'s Session? - It\'s a decentralized, encrypted messaging app - So it doesn\'t collect my personal information or my conversation metadata? How does it work? - Using a combination of advanced anonymous routing and end-to-end encryption technologies. - Friends don\'t let friends use compromised messengers. You\'re welcome. - Say hello to your Session ID - Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design. - Restore your account - Enter the recovery phrase that was given to you when you signed up to restore your account. - Enter your recovery phrase - Pick your display name - This will be your name when you use Session. It can be your real name, an alias, or anything else you like. - Enter a display name - Please pick a display name - Please pick a shorter display name - Recommended - Please Pick an Option - You don\'t have any contacts yet - Start a Session - Are you sure you want to leave this group? - "Couldn't leave group" - Are you sure you want to delete this conversation? - Conversation deleted - Your Recovery Phrase - Meet your recovery phrase - Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don\'t give it to anyone. - Hold to reveal - You\'re almost finished! 80% - Secure your account by saving your recovery phrase - Tap and hold the redacted words to reveal your recovery phrase, then store it safely to secure your Session ID. - Make sure to store your recovery phrase in a safe place - Path - Session hides your IP by bouncing your messages through several Service Nodes in Session\'s decentralized network. These are the countries your connection is currently being bounced through: - You - Entry Node - Service Node - Destination - Learn More - New Session - Enter Session ID - Scan QR Code - Scan a user\'s QR code to start a session. QR codes can be found by tapping the QR code icon in account settings. - Enter Session ID of recipient - Users can share their Session ID by going into their account settings and tapping \"Share Session ID\", or by sharing their QR code. - Session needs camera access to scan QR codes - Grant Camera Access - New Closed Group - Enter a group name - You don\'t have any contacts yet - Start a Session - Please enter a group name - Please enter a shorter group name - Please pick at least 1 group member - A closed group cannot have more than 100 members - Join Open Group - Couldn\'t join group - Open Group URL - Scan QR Code - Scan the QR code of the open group you\'d like to join - Enter an open group URL - Settings - Enter a display name - Please pick a display name - Please pick a shorter display name - Privacy - Notifications - Chats - Devices - Invite - Recovery Phrase - Clear Data - Help us Translate Session - Notifications - Notification Style - Notification Content - Privacy - Chats - Notification Strategy - Change name - Unlink device - Your Recovery Phrase - This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device. - Clear All Data - This will permanently delete your messages, sessions, and contacts. - QR Code - View My QR Code - Scan QR Code - Scan someone\'s QR code to start a conversation with them - Scan Me - This is your QR code. Other users can scan it to start a session with you. - Share QR Code - Contacts - Closed Groups - Open Groups - You don\'t have any contacts yet + Continua + Copia + URL invàlida + Copiat al porta-retalls + Següent + Comparteix + ID de Session invàlid + Cancel·la + El teu ID de Session + El teu Session comença aquí... + Crea un ID de Session + Continua el teu Session + Què és Session? + És una app xifrada i descentralitzada + Per tant, no agafa les meves dades personals o les metadades de les meves converses? Com funciona? + Fent servir una combinació de tecnologies d\'encaminament anònim avançat i un xifratge d\'extrem a extrem. + Amics no deixis que els amics facin servir missatgeries compromeses. Sigueu benvinguts. + Digues hola al teu ID de Session + El teu ID de Session és l\'adreça única que els usuaris poden utilitzar per a contactar-te a Session. Sense cap connexió amb la teva identitat real, el teu ID de Session ID per disseny és totalment anònim i privat. + Restableix el teu compte + Introdueix la frase de recuperació que se\'t va proporcionar quan et vas registrar per a restaurar el compte. + Introdueix la frase de recuperació + Tria el nom que es mostrarà + Aquest serà el teu nom quan facis servir Session. Pot ser el teu nom real, un àlies o qualsevol altra cosa que t\'agradi. + Escriu el nom a mostrar + Tria un nom a mostrar, si us plau + Selecciona un nom de visualització més curt + Recomanat + Selecciona una opció, si us plau + Encara no tens cap contacte + Enceta un Session + Segur que vols abandonar aquest grup? + "No s'ha pogut marxar del grup" + Estàs segur que vols esborrar aquesta conversa? + S\'ha suprimit la conversa + La teva frase de recuperació + Coneix la teva frase de recuperació + La teva frase de recuperació és la clau principal del teu ID de Session — pots fer-la servir per a restaurar la teva ID de Session si perds l\'accés al dispositiu. Emmagatzema la frase de recuperació en un lloc segur i no la donis a ningú. + Mantingues premut per a revelar + Gairebé has acabat! 80% + Protegeix el teu compte desant la frase de recuperació + Mantingues premudes les paraules redactades per a mostrar la teva frase de recuperació. Desa-la de manera segura per a protegir la teva ID de Session. + Assegura\'t d\'emmagatzemar la frase de recuperació en un lloc segur + Ruta + Session amaga la teva IP rebotant els teus missatges a través de diversos nodes de servei de la xarxa descentralitzada de Session. Aquests són els països en què s\'està rebotant la teva connexió actualment: + Tu + Node d\'entrada + Node de servei + Destinació + Aprèn-ne més + Nova sessió + Introdueix la teva ID de Session + Escaneja el codi QR + Escaneja el codi QR d’un usuari per a iniciar una sessió. Es poden trobar codis QR tocant la icona de codi QR a la configuració del compte. + Introdueix la ID de Session del destinatari + Els usuaris poden compartir el seu ID de Session accedint a la configuració del compte i tocant \'Comparteix la ID de Session\' o compartint el seu codi QR. + Session necessita accés a la càmera per escanejar codis QR + Permet accés a la càmera + Nou grup tancat + Introdueix un nom de grup + Encara no tens cap contacte + Enceta un Session + Entra un nom de grup, si us plau + Entra un nom de grup més curt, si us plau + Com a mínim, tria 1 membre per al grup, si us plau + Un grup tancat no pot tenir més de 100 membres + Uneix-te a un Grup Obert + No t\'has pogut unir al grup + URL de Grup Obert + Escaneja el codi QR + Escaneja el codi QR del grup obert que t\'agradaria unir-t\'hi + Entra la URL d\'un grup obert + Configuració + Entre un nom a mostrar + Tria un nom a mostrar, si us plau + Tria un nom a mostra més curt, si us plau + Privadesa + Notificacions + Xats + Dispositius + Convida + Frase de recuperació + Neteja les dades + Ajuda\'ns a traduir Session + Notificacions + Estil de notificacions + Contingut de notificacions + Privadesa + Xats + Estratègia de les notificacions + Canvia el nom + Desenllaça el dispositiu + La teva frase de recuperació + Aquesta és la teva frase de recuperació. Pots restaurar-ne o migrar-ne la teva ID de Session cap a un nou dispositiu. + Esborra totes les dades + Això esborrarà tots els missatges, sessions i contactes permanentment. + Codi QR + Mostra el meu codi QR + Escaneja el codi QR + Escaneja el codi QR d\'algú altre per a encetar-hi una conversa + Escaneja\'m + Aquest és el teu codi QR. Els altres usuaris el poden escanejar per a encetar una conversa amb tu. + Comparteix un codi QR + Contactes + Grups tancats + Grups oberts + Encara no hi tens cap contacte - Apply - Done - Edit Group - Enter a new group name - Members - Add members - Group name can\'t be empty - Please enter a shorter group name - Groups must have at least 1 group member - Remove user from group - Select Contacts - Secure session reset done - Theme - Day - Night - System default - Copy Session ID - Attachment - Voice Message - Details - Failed to activate backups. Please try again or contact support. - Restore backup - Select a file - Select a backup file and enter the passphrase it was created with. - 30-digit passphrase - This is taking a while, would you like to skip? - Link a Device - Or join one of these… - Message Notifications - There are two ways Session can notify you of new messages. - Fast Mode - Slow Mode - You’ll be notified of new messages reliably and immediately using Google’s notification servers. - Session will occasionally check for new messages in the background. - Recovery Phrase - Session is Locked - Tap to Unlock - Enter a nickname - Invalid public key + Aplica + Fet + Edita el Grup + Introdueix un nou nom del grup + Membres + Afegeix membres + El Nom del grup no pot ser buit + Introdueix un nom de grup mes curt, si us plau + Els grups han de tenir com a mínim 1 membre + Elimina l\'usuari del grup + Selecciona contactes + S\'ha fet el restabliment de la sessió segura + Tema + Dia + Nit + Per defecte del sistema + Copia la ID de Session + Adjunt + Missatge de veu + Detalls + No s\'ha pogut activar la llicència. Torneu-ho a intentar o poseu-vos en contacte amb el servei de suport. + Restaura la còpia de seguretat + Selecciona un fitxer + Selecciona un fitxer de còpia de seguretat i entra la frase de pas amb el qual va ser creat. + Frase de pas de 30 dígits + Això triga un xic, t\'ho vols saltar? + Enllaça un dispositiu + O uneix-te a alguns d\'aquests… + Notificacions de missatge + Hi ha dues maneres per les quals Session et pot notificar els missatges nous. + Mode ràpid + Mode lent + Es notificaran els missatges de forma immediata i fiable fent servir els servidors de notificació de Google. + Session ocasionalment comprovarà en pla secundari si hi ha nous missatges. + Frase de recuperació + El Session està blocat + Toca per a desblocar + Introdueix una sobrenom + Clau pública invàlida diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 5f8748bf74..71426bf4a7 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -4,7 +4,7 @@ Ja Nej Slet - Ban + Udeluk Vent venligst... Gem Egen note @@ -18,7 +18,7 @@ %d besked pr. samtale %d beskeder pr. samtale - Slet alle gamle beskeder? + Slet alle gamle beskeder nu? Dette vil øjeblikkeligt reducere alle samtaler til den nyeste besked. Dette vil øjeblikkeligt reducere alle samtaler til de %d nyeste beskeder @@ -61,11 +61,11 @@ Vedhæft fil Vælg kontaktinformation Beklager, der opstod en fejl ved vedhæftning af fil - Message - Compose - Muted until %1$s - %1$d members - Community Guidelines + Besked + Ny meddelelse + Lydløs indtil %1$s + %1$d medlemmer + Retningslinjer For Fællesskabet Ugyldig modtager! Føjet til startskærm Forlad gruppe? @@ -77,14 +77,14 @@ Vedhæftningen overskrider max. grænsen for filstørrelser, for den type af meddelelse du sender Fejl ved lydoptagelse! Der er ingen app tilgængelig på enheden, der kan åbne linket - Add members - Join %s - Are you sure you want to join the %s open group? + Tilføj medlemmer + Tilmeld dig %s + Er du sikker på, at du vil deltage i den åbne gruppe %s? For at sende talebeskeder skal du give Session tilladelse til at tilgå mikrofonen Session kræver tilladelse til at tilgå mikrofonen for at kunne sende lydfiler, hvilket det er blevet nægtet. Gå venligst via appens menu til Indstillinger, vælg \"Tilladelser\" og tilvælg \"Mikrofon\" Session kræver tilladelse til at tilgå dit kamera, for at kunne tage billeder og optage video Session kræver tilladelse til at tilgå dit kamera, for at kunne tage billeder eller optage video, hvilket det er blevet nægtet. Gå venligst via appens menu til Indstillinger, vælg \"Tilladelser\" og tilvælg \"Kamera\" - Session kræver tilladelse til at tilgå kameraet, for at tage billeder og video + Session kræver tilladelse til at tilgå kameraet, for at kunne tage billeder eller videoer. %1$s%2$s %1$d af %2$d Ingen resultater @@ -102,7 +102,7 @@ Dette vil slette den valgte besked permanent. Dette sletter %1$d valgte beskeder permanent - Ban this user? + Udeluk denne bruger? Gem i hukommelsen? At gemme denne fil på disk vil tillade alle andre apps at tilgå den.\n\nFortsæt? @@ -126,8 +126,8 @@ SMS Sletter Sletter beskeder... - Banning - Banning user… + Udelukker + Udelukker bruger… Original besked blev ikke fundet Original besked er ikke længere tilgængelig @@ -140,7 +140,7 @@ Ingen Nu - %d min + %d min. I dag I går @@ -234,14 +234,14 @@ udveksel besked! %s er på Session! Beskeder med tidsudløb deaktiveret Beskeder udløber efter %s - %s took a screenshot. + %s tog et screenshot. Media saved by %s. Sikkerhedsnummer ændret Dit sikkerhedsnummer med %s er ændret Markerét som verificeret Markerét som ubekræftet - This conversation is empty - Open group invitation + Denne samtale er tom + Åben gruppe invitation Session opdatering En ny version af Session er tilgængelig, tap for at opdatere @@ -283,7 +283,7 @@ udveksel besked! Standard Opkald Registrerede fejl - Backups + Sikkerhedskopier Låse status App opdateringer Andet @@ -352,11 +352,11 @@ udveksel besked! Spil Pause - Download + Hent - Join - Open group invitation - Pinned message + Deltag + Åben gruppe invitation + Fastgjort besked Community guidelines Read @@ -448,7 +448,7 @@ udveksel besked! Rød Blå Orange - Cyan + Turkis Magenta Hvid Ingen @@ -488,7 +488,7 @@ udveksel besked! Besked detaljer Kopiér tekst Slet besked - Ban user + Udeluk bruger Send besked igen Svar på besked @@ -548,28 +548,28 @@ udveksel besked! Timeout for inaktiv skærmlås Ingen - Copy public key + Kopiér offentlig nøgle - Continue - Copy - Invalid URL - Copied to clipboard - Next - Share - Invalid Session ID - Cancel - Your Session ID + Fortsæt + Kopiér + Ugyldig Webadresse + Kopieret til udklipsholder + Næste + Del + Ugyltigt Sessions-ID + Annuller + Dit Sessions-ID Your Session begins here... - Create Session ID + Opret Session-ID Continue Your Session - What\'s Session? - It\'s a decentralized, encrypted messaging app - So it doesn\'t collect my personal information or my conversation metadata? How does it work? - Using a combination of advanced anonymous routing and end-to-end encryption technologies. - Friends don\'t let friends use compromised messengers. You\'re welcome. - Say hello to your Session ID + Hvad er Session? + Det er en decentraliseret, krypteret besked app + Så den indsamler ikke mine personlige oplysninger eller min samtale metadata? Hvordan virker det? + Ved hjælp af en kombination af avanceret anonym routing og end-to-end krypteringsteknologier. + Venner lader ikke venner bruge usikre besked tjenester. Du er velkommen. + Sig hej til dit sessions-ID Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design. - Restore your account + Gendan din konto Enter the recovery phrase that was given to you when you signed up to restore your account. Enter your recovery phrase Pick your display name diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index 7fd405a09f..b25d5df018 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -301,7 +301,7 @@ Μη έγκυρη συντόμευση - Συνεδρία + Session Νέο μήνυμα @@ -553,7 +553,7 @@ Συνέχεια Αντιγραφή - Μη έγκυρη διεύθυνση URL + Μη έγκυρη διεύθυνση Αντιγράφηκε στο πρόχειρο Επόμενο Διαμοιρασμός @@ -634,7 +634,7 @@ Πρόσκληση Φράση Ανάκτησης Εκκαθάριση Δεδομένων - Βοηθήστε μας να μεταφράσουμε τη συνεδρία + Βοηθήστε μας να μεταφράσουμε τo Session Ειδοποιήσεις Στυλ ειδοποιήσεων Περιεχόμενο Ειδοποιήσεων @@ -696,6 +696,6 @@ Φράση Ανάκτησης Το Session είναι κλειδωμένο Πατήστε για ξεκλείδωμα - Enter a nickname - Invalid public key + Εισαγωγή ψευδώνυμου + Μη έγκυρο δημόσιο κλειδί diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index d337b9a436..1e2ea59731 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -4,11 +4,11 @@ No Eliminar - Ban + Banear Por favor, espera ... Guardar Notas personales - Version %s + Versión %s Nuevo mensaje @@ -37,7 +37,7 @@ Session necesita acceso a los contactos en tu teléfono para adjuntar información de contactos en tus chats. Por favor, ve al menú de configuración de la aplicación, selecciona «Permisos» y activa «Contactos». Session necesita acceso a la cámara para tomar fotos y verificar las cifras de seguridad de tus chats. Por favor, ve al menú de configuración de la aplicación, selecciona «Permisos» y activa «Cámara». - ¡Fallo al reproducir sonido! + ¡Fallo al reproducir el audio! Hoy Ayer @@ -61,11 +61,11 @@ Añadir archivo adjunto Seleccionar información de contacto Lo sentimos, ha habido un fallo al adjuntar el archivo. - Message - Compose - Muted until %1$s - %1$d members - Community Guidelines + Mensaje + Escribir + Silenciado hasta %1$s + %1$d miembros + Normas de la Comunidad ¡Destinatario inválido! Añadido a la pantalla de inicio ¿Abandonar grupo? @@ -77,9 +77,9 @@ El adjunto excede los límites de tamaño para el mensaje. ¡No se ha podido grabar la nota de voz! No hay ninguna aplicación disponible para abrir este enlace. - Add members - Join %s - Are you sure you want to join the %s open group? + Añadir miembros + Unirse a %s + ¿Estás seguro de que quieres unirte al grupo abierto %s? Para enviar notas de voz y hacer llamadas, permite a Session acceder al micrófono. Session necesita acceso al micrófono para enviar notas de voz. Por favor, ve al menú de configuración de la aplicación, selecciona «Permisos» y activa «Micrófono». Para hacer fotos y vídeos, permite el acceso de Session a la cámara. @@ -102,7 +102,7 @@ Esto eliminará de forma permanente el mensaje seleccionado. Esto eliminará de forma permanente los %1$d mensajes seleccionados. - Ban this user? + ¿Banear a este usuario? ¿Guardar en el almacenamiento? Guardar este archivo multimedia en el almacenamiento del teléfono permitirá que cualquier otra aplicación en tu dispositivo acceda a él.\n\n¿Continuar? @@ -126,8 +126,8 @@ SMS Eliminar Eliminando mensajes ... - Banning - Banning user… + Baneando + Baneando usuario… No se encuentra el mensaje original El mensaje original ya no está disponible @@ -234,14 +234,14 @@ de intercambio de claves! ¡%s usa Session! Desaparición de mensajes desactivada. El tiempo de desaparición de mensajes se ha fijado en %s - %s took a screenshot. - Media saved by %s. + %s hizo una captura de pantalla. + Medios guardados por %s. Las cifras de seguridad han cambiado Tus cifras de seguridad con %s han cambiado. Marcado como verificado Marca de verificación retirada - This conversation is empty - Open group invitation + Esta conversación está vacía + Abrir invitación del grupo Actualizar Session Una nueva versión de Session está disponible. Toca para actualizar. @@ -354,11 +354,11 @@ de intercambio de claves! Pausa Descargar - Join - Open group invitation - Pinned message - Community guidelines - Read + Unirse + Abrir invitación del grupo + Mensaje fijado + Normas de la comunidad + Leer Audio Vídeo @@ -488,7 +488,7 @@ de intercambio de claves! Detalles del mensaje Copiar texto Eliminar mensaje - Ban user + Banear usuario Reenviar mensaje Responder al mensaje @@ -548,7 +548,7 @@ de intercambio de claves! Tiempo de inactividad para el bloqueo de pantalla Ninguno - Copy public key + Copiar clave pública Continuar Copiar @@ -589,15 +589,15 @@ de intercambio de claves! Guarda tu frase de recuperación Tu frase de recuperación es la llave maestra de tu ID de Session, puedes usarla para recuperar tu ID de Session en caso de pérdida de acceso a tu dispositivo. Guarda tu frase de recuperación en un lugar seguro y no se la digas a nadie. Mantén pulsado para revelar - You\'re almost finished! 80% + ¡Ya casi has terminado! 80% Protege tu cuenta guardando tu frase de recuperación Toca y mantén presionadas las palabras redactadas para revelar tu frase de recuperación, después guárdala de manera segura para proteger tu ID de Session. Asegúrate de guardar tu frase de recuperación en un lugar seguro Ruta Session oculta tu dirección IP haciendo rebotar tus mensajes a través de los Nodos de servicio de la red descentralizada de Session. Estos son los países por los que tu conexión está siendo rebotada actualmente. - Entry Node - Service Node + Nodo de Entrada + Nodo de Servicio Destino Saber Más Nueva Session @@ -614,8 +614,8 @@ de intercambio de claves! Empezar una Session Por favor, ingresa un nombre de grupo Por favor, ingresa un nombre de grupo más corto - Please pick at least 1 group member - A closed group cannot have more than 100 members + Por favor, elige al menos 1 miembro del grupo + Un grupo cerrado no puede tener más de 100 miembros Únete al grupo abierto No te pudiste unir al grupo URL de grupo abierto @@ -630,10 +630,10 @@ de intercambio de claves! Notificaciones Chats Dispositivos - Invite + Invitar Frase de recuperación Borrar datos - Help us Translate Session + Ayúdanos a traducir Session Notificaciones Estilo de Notificación Contenido de Notificación @@ -650,51 +650,51 @@ de intercambio de claves! Ver mi código QR Escanear código QR Escanea el código QR de una persona para comenzar una conversación con ella - Scan Me + Escanéame Este es tu código QR. Otros usuarios pueden escanearlo para empezar una sesión contigo. Compartir código QR Contactos Grupos cerrados Grupos abiertos - You don\'t have any contacts yet + Aún no tienes contactos - Apply - Done - Edit Group - Enter a new group name - Members - Add members - Group name can\'t be empty - Please enter a shorter group name - Groups must have at least 1 group member - Remove user from group - Select Contacts - Secure session reset done - Theme - Day - Night - System default - Copy Session ID - Attachment - Voice Message - Details - Failed to activate backups. Please try again or contact support. - Restore backup - Select a file - Select a backup file and enter the passphrase it was created with. - 30-digit passphrase - This is taking a while, would you like to skip? - Link a Device - Or join one of these… - Message Notifications - There are two ways Session can notify you of new messages. - Fast Mode - Slow Mode - You’ll be notified of new messages reliably and immediately using Google’s notification servers. - Session will occasionally check for new messages in the background. - Recovery Phrase - Session is Locked - Tap to Unlock - Enter a nickname - Invalid public key + Aplicar + Hecho + Editar grupo + Ingresa el nombre del nuevo grupo + Miembros + Añadir miembros + El nombre del grupo no puede estar vacío + Por favor, ingresa un nombre de grupo más corto + Los grupos deben tener al menos 1 miembro + Eliminar usuario del grupo + Elegir contactos + Reinicio seguro de sesión hecho + Tema + Día + Noche + Predeterminado del sistema + Copiar ID de Session + Adjunto + Mensaje de voz + Detalles + Error al activar las copias de seguridad. Por favor, inténtalo de nuevo o contacta con el soporte. + Restaurar copia de seguridad + Seleccionar archivo + Seleccione un archivo de copia de seguridad e introduzca la clave de acceso con la que fue creada. + Clave de acceso de 30 dígitos + Esto está tomando un tiempo, ¿te gustaría saltarlo? + Vincular un Dispositivo + O únete a uno de estos… + Notificaciones de Mensajes + Hay dos maneras en las que Session puede notificarle de nuevos mensajes. + Modo Rápido + Modo Lento + Se le notificará de los nuevos mensajes de forma fiable e inmediata usando los servidores de notificaciones de Google. + Session comprobará ocasionalmente si hay nuevos mensajes en segundo plano. + Frase de recuperación + Session está Bloqueado + Toca para Desbloquear + Escriba un apodo + Clave pública no válida diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index e06ee49d19..658f872e27 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -1,34 +1,34 @@ - Session + نشست بله خیر حذف - Ban - لطفاً صبر کنید... + مسدود + لطفاً شکیبا باشید... ذخیره یادداشت به خود - Version %s + نگارش %s پیام جدید - \+%d + %d+\ %d پیام در هر گفتگو %d پیام در هر گفتگو - همه ی پیام‌های قدیمی حذف شوند؟ + همه‌ی پیام‌های قدیمی حذف شوند؟ این گزینه ، بلافاصله تمامی مکالمات شما را به %d پیام آخر ، کاهش خواهد داد. - این گزینه ، بلافاصله تمامی مکالمات شما را به %d پیام آخر ، کاهش خواهد داد. + این گزینه فورا تمامی مکالمات شما را به %d پیام آخر کاهش خواهد داد. حذف - روشن - خاموش + فعال + غیرفعال (تصویر) - (صوتی) + (صدا) (ویدئو) (پاسخ) @@ -61,32 +61,32 @@ پیوست فایل انتخاب اطلاعات تماس متأسفم، در تنظیم فایل پیوست شما خطا وجود دارد. - Message - Compose - Muted until %1$s - %1$d members - Community Guidelines + پیام + نوشتن + بی صدا تا %1$s + %1$d عضو + راهنمای انجمن گیرنده نامعتبر است! - اضافه شده به صفحه اصلی + به صفحه اصلی اضافه شد ترک گروه؟ - آیا شما مطمئن هستید که میخواهید این گروه را ترک کنید؟ + آیا مطمئن هستید که می‌خواهید این گروه را ترک کنید؟ خطا در ترک کردن گروه رفع انسداد این مخاطب؟ - دریافت پیام و تماس از این مخاطب مجددا برای شما فعال شده است. + دریافت پیام و تماس از این مخاطب دوباره برای شما فعال خواهد شد. رفع انسداد - حجم فایل پیوست شده بیشتر از اندازه مجاز تعیین شده برای این نوع پیام است. - قادر به ضبط صدا نیست! - هیچ برنامه ای به منظور پشتیبانی از لینک برروی دستگاه شما وجود ندارد. - Add members - Join %s - Are you sure you want to join the %s open group? + اندازه‌ی پیوست بیشتر از حد این نوع پیام ارسالی است. + عدم توانایی ضبط صدا! + هیچ برنامه ای برای اجرای این لینک روی دستگاه شما در دسترس نیست. + افزودن اعضا + پیوستن به %s + آیا اطمینان دارید که می‌خواهید به گروه باز %s بپیوندید؟ برای ارسال صدا، به Session اجازه دهید به میکروفن شما دسترسی پیدا کند. Session برای ارسال صدا، نیازمند دسترسی به میکروفن دارد ولی این دسترسی قطع شده است. لطفا برای ادامه دادن، به بخش منوی تنظیمات برنامه رفته، \"اجازه ها\" را انتخاب کرده و گزینه ی \"میکروفن\" را فعال نمایید. برای گرفتن تصاویر و ویدیو، به Session اجازه دسترسی دوربین را بدهید. Session برای گرفتن عکس و فیلم، نیازمند دسترسی به دوربین دارد ولی این دسترسی برای همیشه قطع شده است. لطفا برای ادامه دادن، به بخش منوی تنظیمات برنامه رفته، \"اجازه ها\" را انتخاب کرده و گزینه ی \"دوربین\" را فعال نمایید. Session برای عکس گرفتن یا ضبط ویدیو نیاز به دسترسی به دوربین دارد %1$s %2$s - %1$d of %2$d + %1$d از %2$d بدون نتیجه @@ -102,7 +102,7 @@ این به صورت دائم %1$d پیامهای انتخاب شده را حذف میکند. این به صورت دائم %1$d پیامهای انتخاب شده را حذف میکند. - Ban this user? + این کاربر مسدود شود؟ ذخیره درحافظه؟ ذخیره همه %1$d رسانه ها به حافظه دستگاه ، باعث خواهد شد که همه برنامه های دیگر نصب شده در دستگاه شما ، به این اطلاعات دسترسی پیدا کنند. \n\n آیا مایل به ادامه هستید؟ @@ -126,8 +126,8 @@ پیامک در حال حذف کردن حذف پیام ها... - Banning - Banning user… + مسدودسازی + مسدودسازی… پیام اصلی یافت نشد پیام اصلی دیگر در دسترس نیست @@ -183,7 +183,7 @@ ارسال به %s یک عنوان اضافه کنید... - An item was removed because it exceeded the size limit + یک مورد به دلیل بزرگتراز حد بودن پاک شد دوربین در دسترس نیست پیام به %s @@ -234,14 +234,14 @@ %s در Session است! پیام های نابود شونده غیر فعال شد زمان پنهان شدن پیام بر روی %s تنظیم شد - %s took a screenshot. - Media saved by %s. + %s از صفحه یک عکس گرفت. + رسانه توسط %s ذخیره شد. شماره امنیتی تغییر یافت شماره امنیتی شما با %s تغییر یافت. شما به عنوان تایید شده علامت زدید شما به عنوان تایید نشده علامت زدید - This conversation is empty - Open group invitation + این مکالمه خالی است + بازکردن دعوتنامه گروه بروزرسانی Session نسخه جدید Session در دسترس است، برای آپدیت اینجا را تپ کنید @@ -261,8 +261,8 @@ شما نوع رسانه پشتیبانی نشده پیش نویس - Session needs the Storage permission in order to save to external storage, but it has been permanently denied. Please continue to app settings, select \"Permissions\", and enable \"Storage\". - Unable to save to external storage without permissions + Session برای ذخیره در حافظه‌ی خارجی نیاز به دسترسی به حافظه دارد اما این دسترسی به طور همیشگی رد شده است. لطفا به بخش برنامه در تنظیمات تلفن همراه خود رفته و پس از یافتن Session وارد بخش دسترسی ها شده و گزینه حافظه را فعال کنید. + نمی‌توان بدون دسترسی‌ها در حافظه‌ی خارجی ذخیره‌سازی نمود حذف پیام؟ این باعث حذف این پیام به صورت دائم خواهد شد. @@ -282,10 +282,10 @@ پیش فرض تماس ها - Failures + شکست‌ها پشتیبان ها وضعیت قفل - App updates + به‌روزآوری‌های برنامه دیگر پیام ها ناشناخته @@ -293,14 +293,14 @@ پاسخگوی سریع هنگامی که نرم‌افزار Session قفل باشد غیرفعال می‌شود. بروز خطا در ارسال پیام! - Saved to %s + در %s ذخیره شد ذخیره شد جستجو میانبر نامعتبر - Session + نشست پیام جدید @@ -334,10 +334,10 @@ پیوست تصویر کوچک تغییر حالت نگه دارنده پیوست دوربین سریع فایل صوتی خود را ضبط کرده و ارسال کنید. - Lock recording of audio attachment + قفل کردن پیوست صدای ضبط شده فعال کردن Session برای SMS - Slide to cancel + برای لغو بکشید لغو پیام رسانه @@ -354,11 +354,11 @@ مکث دریافت - Join - Open group invitation - Pinned message - Community guidelines - Read + \"پیوستن\" + بازکردن دعوتنامه گروه + پیام سنجاق شده + راهنمای انجمن + خواندن صوت ویدئو @@ -463,10 +463,10 @@ پیش فرض کیبورد ناشناس خواندن رسید ها - If read receipts are disabled, you won\'t be able to see read receipts from others. + اگر رسید خواندن غیرفعال باشد، شما نمی‌توانید رسید خواندن دیگران را ببینید. نشانگر های نوشته اگر نشانگر های نوشته غیرفعال می باشند، شما قادر نخواهید بود تا نشانگر های نوشته سایرین را ببینید. - Request keyboard to disable personalized learning + درخواست غیرفعال‌سازی یادگیری شخصی از صفحه‌کلید روشن تیره برش دادن پیام @@ -488,7 +488,7 @@ جزییات پیام کپی متن حذف پیام - Ban user + مسدود کردن کاربر پیام فرستادن مجدد پاسخ به پیام @@ -529,13 +529,13 @@ ادامه حالا نه پشتیبان ها روی یک حافظه خارجی ذخیره خواهند شد و با عبارت عبور زیر رمزنگاری خواهند شد. شما باید عبارت عبور زیر را داشته باشید تا پشتیبان خود را بازیابی کنید. - I have written down this passphrase. Without it, I will be unable to restore a backup. + من این گذرمتن را نوشته‌ام. بدون آن نخواهم توانست یک پشتیبان را بازگردانی کنم. پرش - Cannot import backups from newer versions of Session - Incorrect backup passphrase + نمی‌توان از نگارش‌های جدید نشست پشتیبان‌ها را وارد کرد + گذرمتن پشتیبان اشتباه است فعال سازی پشتیبان های محلی؟ فعال سازی پشتیبان ها - Please acknowledge your understanding by marking the confirmation check box. + لطفا با علامت زدن تأیید آگاهی خود را نشان دهید. حذف پشتیبان ها؟ غیرفعال سازی و پاک کردن همه پشتیبان های محلی؟ حذف پشتیبان ها @@ -544,11 +544,11 @@ %d پیام تا به الان هرگز قفل صفحه - Lock Session access with Android screen lock or fingerprint - Screen lock inactivity timeout + قفل کردن دسترسی به نشست با قفل صفحه اندروید با اثر انگشت + زمان قفل صفحه هنگام عدم فعالیت هیچ کدام - Copy public key + رونوشت کلید عمومی ادامه کپی کردن @@ -589,7 +589,7 @@ این عبارت بازیابی شماست عبارت بازیابی شما کلید اصلی شناسه‌ی Session شما است - در صورت عدم دسترسی به دستگاه خود می توانید از آن برای بازگرداندن شناسه‌ی Session خود استفاده کنید. عبارت بازیابی خود را در مکانی امن ذخیره کنید و آن را به کسی ندهید. نگه دارید تا نشان داده شود - You\'re almost finished! 80% + شما تقریبا تمام کردید! ۸۰٪ با ذخیره کردن عبارت بازیابی، از حساب خود را محافظت کنید برای فاش کردن عبارت بازیابی، بر روی کلمات redacted ضربه زده و نگه دارید، سپس با خیال راحت آن را ذخیره کنید تا از شناسه‌ی Session خود محافظت نمایید. حتماً عبارت بازیابی خود را در مکانی امن ذخیره کنید @@ -614,8 +614,8 @@ شروع Session لطفا یک نام گروه وارد کنید لطفا نام گروه کوتاه‌تری وارد کنید - Please pick at least 1 group member - A closed group cannot have more than 100 members + لطفا حداقل یک عضو گروه انتخاب کنید + یک گروه خصوصی نمی‌تواند بیش از ۱۰۰ عضو داشته باشد به گروه باز بپیوندید امکان پیوستن به گروه نیست آدرس اینترنتی گروه را باز کنید @@ -630,10 +630,10 @@ اعلان‌ها گفت‌وگوها دستگاه‌ها - Invite + دعوت کردن عبارت بازیابی پاک کردن اطلاعات - Help us Translate Session + به ما کمک کنید که سشن را ترجمه کنیم اعلان‌ها نحوه اطلاع‌رسانی محتوای اعلان @@ -650,51 +650,51 @@ مشاهده کد QR من اسکن کد QR برای شروع مکالمه با دیگران، کد QR شخصی را اسکن کنید - Scan Me + مرا اسکن کن این کد QR شماست. سایر کاربران می‌توانند برای شروع Session با شما آن را اسکن کنند. کد QR را به اشتراک بگذارید مخاطبین گروه‌های خصوصی گروه‌های باز - You don\'t have any contacts yet + شما هنوز هیچ مخاطبی ندارید - Apply - Done - Edit Group - Enter a new group name - Members - Add members - Group name can\'t be empty - Please enter a shorter group name - Groups must have at least 1 group member - Remove user from group - Select Contacts - Secure session reset done - Theme - Day - Night - System default - Copy Session ID - Attachment - Voice Message - Details - Failed to activate backups. Please try again or contact support. - Restore backup - Select a file - Select a backup file and enter the passphrase it was created with. - 30-digit passphrase - This is taking a while, would you like to skip? - Link a Device - Or join one of these… - Message Notifications - There are two ways Session can notify you of new messages. - Fast Mode - Slow Mode - You’ll be notified of new messages reliably and immediately using Google’s notification servers. - Session will occasionally check for new messages in the background. - Recovery Phrase - Session is Locked - Tap to Unlock - Enter a nickname - Invalid public key + اعمال + انجام شد + ویرایش گروه + نام گروهی جدید وارد کنید + اعضا + افزودن اعضا + نام گروه نمی‌تواند خالی باشد + لطفا نام گروه کوتاه‌تری وارد کنید + گروه‌ها باید حداقل ۱ عضو داشته باشند + پاک کردن عضو از گروه + انتخاب مخاطب‌ها + تنظیم مجدد جلسه امن انجام شد + قالب + روز + شب + پیش فرض سیستم + رونوشت شناسه نشست شما + پیوست + پیام صوتی + جزئيات + فعال‌سازی پشتیبان‌ها شکست خورد. لطفا دوباره امتحان کنید یا با پشتیبانی تماس بگیرید. + بازگردانی پشتیبان + انتخاب فایل + یک فایل پشتیبان انتخاب و گذرمتنی که با آن ساخته شده است را وارد کنید. + گذرمتن ۳۰رقمی + این مقداری طول می‌کشد، دوست دارید ردش کنید؟ + اتصال به یک دستگاه + یا به یکی از این‌ها بپیوندید… + اعلان‌های پیام + دو راه برای اطلاع رسانی شما از پیام‌های جدید در نشست وجود دارد. + حالت سریع + حالت آهسته + شما سریع و مطمئن با استفاده از سرور‌های اطلاع‌رسانی گوگل از پیام‌های جدید مطلع می‌شوید. + نشست هرازگاهی در پس زمینه وجود پیام‌های جدید را بررسی می‌کند. + متن بازیابی + نشست قفل شده‌است +  ﺑﺮﺍﯼ ﮔﺸﻮﺩﻥ ﺿﺮﺑﻪ ﺑﺰﻧﻴﺪ + یک نام مستعار وارد کنید + کلید عمومی نامعتبر diff --git a/app/src/main/res/values-hu/strings.xml b/app/src/main/res/values-hu/strings.xml index 7f345947b9..7813eaadf4 100644 --- a/app/src/main/res/values-hu/strings.xml +++ b/app/src/main/res/values-hu/strings.xml @@ -1,14 +1,14 @@ - Session + Ülés Igen Nem Törlés - Ban + Kitiltás Kérlek várj... Mentés Privát feljegyzés - Version %s + Verzió %s Új üzenet @@ -61,11 +61,11 @@ Melléklet csatolása Válassz kontakt infót Sajnos hiba történt a melléklet csatolása során. - Message - Compose - Muted until %1$s - %1$d members - Community Guidelines + Üzenet + Összeállít + Eddig van némítva %1$s + %1$d tagok + Közösségi irányelvek Érvénytelen címzett! Hozzáadva a kezdőképernyőhöz Kilépsz a csoportból? @@ -77,9 +77,9 @@ A melléklet mérete meghaladja az adott típushoz tartozó mérethatárt. Nem lehet hangot rögzíteni! Nem található eszközödön megfelelő alkalmazás a hivatkozás kezeléséhez. - Add members - Join %s - Are you sure you want to join the %s open group? + Tagok hozzáadása + Csatlakozott %s + Biztosan csatlakozni szeretne a(z) %s nyitott csoportban? Hangüzenetek küldéséhez engedélyezd, hogy a Session hozzáférhessen a mikrofonhoz! A Session-nak szüksége van a Mikrofon engedélyre, hogy hangüzeneteket küldhessen, de ez jelenleg nincs megadva. Kérlek menj az alkalmazásbeállításokhoz, válaszd az \"Engedélyek\"-et és engedélyezd az \"Mikrofon\"-t. Fotók és videók készítéséhez engedélyezd a Session-nak a kamerához való hozzáférést! @@ -102,7 +102,7 @@ Ez végelegesen törölni fogja a kiválasztott üzenetet. Ez véglegesen törölni fogja mind a(z) %1$d db kiválasztott üzenetet. - Ban this user? + Kitiltja ezt a felhasználót? Mentés tárolóra? Ez a média mentése a tárolóra lehetővé teszi bármelyik másik alkalmazásnak a készülékeden, hogy hozzáférjen.\n\nFolytatod? @@ -126,8 +126,8 @@ SMS Törlés Üzenetek törlése... - Banning - Banning user… + Kitiltás + Felhasználó kitiltása… Az eredeti üzenet nem található Az eredeti üzenet már nem érhető el @@ -236,14 +236,14 @@ %s a Session-on van! Eltűnő üzenetek letiltva Eltűnő üzenet ideje beállítva erre: %s - %s took a screenshot. - Media saved by %s. + %s készített egy képernyőképet. + Média mentve ide %s. Megváltozott a biztonsági szám Megváltozott a biztonsági számod vele: %s. Megerősítettnek jelölted Megerősítetlennek jelölted - This conversation is empty - Open group invitation + Ez a beszélgetés üres + Nyilvános csoport meghívó Session frissítés A Session egy új verziója érhető el, koppints a frissítéshez @@ -302,7 +302,7 @@ Érvénytelen parancsikon - Session + Ülés Új üzenet @@ -356,11 +356,11 @@ Szünet Letöltés - Join - Open group invitation - Pinned message - Community guidelines - Read + Csatlalkozás + Nyilvános csoport meghívó + Kitűzött üzenet + Közösségi irányelvek + Olvas Hang Videó @@ -552,17 +552,17 @@ Copy public key - Continue - Copy - Invalid URL - Copied to clipboard - Next - Share - Invalid Session ID - Cancel - Your Session ID + Folytatás + Másolás + Érvénytelen URL + Vágólapra másolva + Következő + Megosztás + Érvénytelen Session azonosító + Mégse + Az ön Session azonosítója Your Session begins here... - Create Session ID + Session azonosító létrehozása Continue Your Session What\'s Session? It\'s a decentralized, encrypted messaging app @@ -574,11 +574,11 @@ Restore your account Enter the recovery phrase that was given to you when you signed up to restore your account. Enter your recovery phrase - Pick your display name - This will be your name when you use Session. It can be your real name, an alias, or anything else you like. - Enter a display name - Please pick a display name - Please pick a shorter display name + Válassza ki a megjelenítendő nevet + Ez lesz az ön neve, amikor használja a Session alkalmazást. Ez lehet a valódi neve, álneve, vagy bármi más, ami tetszik. + Írja be a megjelenítendő nevet + Kérjük, válassza ki a megjelenítendő nevet + Válasszon rövidebb megjelenítési nevet Recommended Please Pick an Option You don\'t have any contacts yet @@ -597,7 +597,7 @@ Make sure to store your recovery phrase in a safe place Path Session hides your IP by bouncing your messages through several Service Nodes in Session\'s decentralized network. These are the countries your connection is currently being bounced through: - You + Te Entry Node Service Node Destination @@ -611,11 +611,11 @@ Session needs camera access to scan QR codes Grant Camera Access New Closed Group - Enter a group name + Írja be a csoport nevét You don\'t have any contacts yet Start a Session - Please enter a group name - Please enter a shorter group name + Kérjük, adja meg a csoport nevét + Írjon be rövidebb csoportnevet Please pick at least 1 group member A closed group cannot have more than 100 members Join Open Group @@ -624,25 +624,25 @@ Scan QR Code Scan the QR code of the open group you\'d like to join Enter an open group URL - Settings - Enter a display name - Please pick a display name - Please pick a shorter display name - Privacy - Notifications - Chats - Devices - Invite + Beállítások + Írja be a megjelenítendő nevet + Válasszon megjelenítési nevet + Válasszon rövidebb megjelenítési nevet + Adatvédelem + Értesítések + Beszélgetések + Eszközök + Meghívás Recovery Phrase - Clear Data - Help us Translate Session + Adataid törlése + Segítsen nekünk a Session lefordításában Notifications - Notification Style + Értesítések stílusa Notification Content - Privacy - Chats + Adatvédelem + Beszélgetések Notification Strategy - Change name + Név módosítása Unlink device Your Recovery Phrase This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device. @@ -655,34 +655,34 @@ Scan Me This is your QR code. Other users can scan it to start a session with you. Share QR Code - Contacts + Kapcsolatok Closed Groups Open Groups You don\'t have any contacts yet - Apply - Done - Edit Group - Enter a new group name - Members - Add members - Group name can\'t be empty - Please enter a shorter group name + Alkalmaz + Kész + Csoport szerkesztése + Írjon be egy új csoportnevet + Tagok + Tagok hozzáadása + A csoport neve nem lehet üres + Írjon be rövidebb csoportnevet Groups must have at least 1 group member Remove user from group Select Contacts Secure session reset done - Theme - Day - Night + Téma + Ki + Be System default - Copy Session ID + Az ön Session azonosító kimásolása Attachment Voice Message Details Failed to activate backups. Please try again or contact support. Restore backup - Select a file + Fájl kiválasztása Select a backup file and enter the passphrase it was created with. 30-digit passphrase This is taking a while, would you like to skip? @@ -690,13 +690,13 @@ Or join one of these… Message Notifications There are two ways Session can notify you of new messages. - Fast Mode - Slow Mode + Gyorsított mód + Lassított mód You’ll be notified of new messages reliably and immediately using Google’s notification servers. Session will occasionally check for new messages in the background. Recovery Phrase Session is Locked - Tap to Unlock - Enter a nickname - Invalid public key + Koppintson a feloldáshoz + Írjon be egy becenevet + Érvénytelen nyilvános kulcs diff --git a/app/src/main/res/values-id/strings.xml b/app/src/main/res/values-id/strings.xml index 9880cfdc60..53227ca728 100644 --- a/app/src/main/res/values-id/strings.xml +++ b/app/src/main/res/values-id/strings.xml @@ -3,35 +3,35 @@ Session Ya Tidak - Delete - Ban - Please wait... + Hapus + Larang + Mohon Tunggu... Simpan Catatan Pribadi Versi %s Pesan baru - \+%d + \+ %d - %d messages per conversation + %d pesan tiap percakapan - Delete all old messages now? + Hapus semua pesan lama? - This will immediately trim all conversations to the %d most recent messages. + Ini akan memangkas semua percakapan menjadi %d percakapan terbaru. Hapus - On - Off + Nyala + Mati - (image) - (audio) - (video) - (reply) + (gambar) + (suara) + (videi) + (balas) - Can\'t find an app to select media. - Session requires the Storage permission in order to attach photos, videos, or audio, but it has been permanently denied. Please continue to the app settings menu, select \"Permissions\", and enable \"Storage\". + Tidak menemukan aplikasi untuk memilih media. + Session membutuhkan izin penyimpanan untuk melampirkan foto, video, atau audio, namun izin tersebut telah ditolak secara permanen. Harap melanjutkan ke menu pengaturan aplikasi, pilih \"Perizinan\", dan izinkan \"Penyimpanan\". Session requires Contacts permission in order to attach contact information, but it has been permanently denied. Please continue to the app settings menu, select \"Permissions\", and enable \"Contacts\". Session requires the Camera permission in order to take photos, but it has been permanently denied. Please continue to the app settings menu, select \"Permissions\", and enable \"Camera\". diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index cab377dec1..3711d8fc69 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -61,7 +61,7 @@ Aggiungi allegato Seleziona informazioni dei contatti Attenzione, c\'è stato un errore nell\'inviare il tuo allegato. - Message + Messaggio Compose Muted until %1$s %1$d members @@ -607,7 +607,7 @@ scambia un altro messaggio! Inserisci la Sessione ID del destinatario Gli utenti possono condividere la propria Sessione ID accedendo alle impostazioni del proprio account e toccando Condividi la Sessione ID o condividendo il proprio codice QR. La Sessione richiede l\'accesso alla fotocamera per scansionare i codici QR - Concedi l\'accesso alla fotocamera + Consenti Accesso Fotocamera Nuovo gruppo chiuso Inserisci un nome per il gruppo Non hai ancora nessun contatto diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e5868e54d7..7ddbc99f4c 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -723,6 +723,6 @@ Секретная фраза Session заблокирован Разблокировать - Enter a nickname - Invalid public key + Введите ник + Неверный публичный ключ diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 0f5335ee95..58667c4e50 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -81,7 +81,7 @@ Príloha presahuje limit veľkosti pre tento typ správy. Nemôžem zaznamenať zvuk! Vo vašom zariadení nie je aplikácia schopná otvoriť tento odkaz. - Add members + Pridať členov Join %s Are you sure you want to join the %s open group? Pre posielanie zvukových správ potrebuje Session prístup k mikrofónu. @@ -158,7 +158,7 @@ Žiadna Teraz - %d min + %d minút Dnes Včera @@ -258,7 +258,7 @@ výmeny kľúčov. %s je na Sessione! Miznúce správy vypnuté Čas miznúcich správ bol nastavený na %s - %s took a screenshot. + %s urobil snímok obrazovky. Media saved by %s. Bezpečnostné číslo sa zmenilo Vaše bezpečnostné číslo s %s sa zmenilo. @@ -380,7 +380,7 @@ výmeny kľúčov. Pozastaviť Stiahnuť - Join + Pripojiť sa Open group invitation Pinned message Community guidelines @@ -516,7 +516,7 @@ výmeny kľúčov. Podrobnosti správy Kopírovať text Zmazať správu - Ban user + Zablokovať užívateľa Odoslať správu znovu Odpovedať na správu @@ -578,14 +578,14 @@ výmeny kľúčov. Copy public key - Continue - Copy - Invalid URL - Copied to clipboard - Next - Share + Pokračovať + Kopírovať + Neplatná URL adresa + Skopírované do schránky + Ďalej + Zdieľať Invalid Session ID - Cancel + Zrušiť Your Session ID Your Session begins here... Create Session ID diff --git a/app/src/main/res/values-sv-rSE/strings.xml b/app/src/main/res/values-sv-rSE/strings.xml index 9b754732e8..e54b4ab035 100644 --- a/app/src/main/res/values-sv-rSE/strings.xml +++ b/app/src/main/res/values-sv-rSE/strings.xml @@ -61,7 +61,7 @@ Bifoga fil Välj kontaktinformation Tyvärr uppstod det ett fel vid bifogning av din fil. - Message + Meddelande Compose Muted until %1$s %1$d members @@ -77,8 +77,8 @@ Den bifogade filen är för stor för den typ av meddelande du försöker skicka. Kunde inte spela in ljud! Det finns ingen app på din enhet som kan hantera den här länken. - Add members - Join %s + Lägg till medlemmar + Gå med %s Are you sure you want to join the %s open group? För att skicka ljudmeddelanden, vänligen ge Session tillgång till din mikrofon. Session behöver behörigheten Mikrofon för att skicka ljudmeddelanden men har nekats den permanent. Fortsätt till inställningsmenyn för Appar och aviseringar, välj \"Behörigheter\" och aktivera \"Mikrofon\". @@ -551,10 +551,10 @@ för nyckelutbyte! Copy public key Continue - Copy + Kopiera Invalid URL Copied to clipboard - Next + Nästa Share Invalid Session ID Cancel diff --git a/app/src/main/res/values-sw/strings.xml b/app/src/main/res/values-sw/strings.xml new file mode 100644 index 0000000000..cabcec4b4f --- /dev/null +++ b/app/src/main/res/values-sw/strings.xml @@ -0,0 +1,699 @@ + + + Session + Ndio + Hapana + Futa + Ban + Tafadhali Subiri... + Hifadhi + Kumbuka kwake + Version %s + + Ujumbe mpya + + /+ 1%d + + + %d message per conversation + %d messages per conversation + + Futa sasa meseji zote za zamani + + This will immediately trim all conversations to the most recent message. + This will immediately trim all conversations to the %d most recent messages. + + Futa + Waka + Zima + + (picha) + (sauti) + (video) + (jibu) + + Nashindwa kupata app ya kuchagua habari + Session inahitaji idhini ya Hifadhi ili kuunganisha picha, video, au sauti, lakini imekataliwa kabisa. Tafadhali endelea kwenye orodha ya mipangilio ya programu, chagua \"Ruhusa\", na uwawezesha \"Hifadhi\". + Session inahitaji idhini ya Mawasiliano ili kuunganisha maelezo ya mawasiliano, lakini imekataliwa kabisa. Tafadhali endelea kwenye orodha ya mipangilio ya programu, chagua \"Ruhusa\", na uwawezesha \"Mawasiliano\". + Session inahitaji ruhusa ya Kamera ili kuchukua picha, lakini imekataliwa kabisa. Tafadhali endelea kwenye orodha ya mipangilio ya programu, chagua \"Ruhusa\", na uwawezesha \"Kamera\". + + Hitilafu ya kucheza sauti! + + leo + jana + Wiki hii + Mwezi huu + + hakuna kivinjari cha web kilichopatikana + + Makundi + + imefeli kutuma, gusa kwa taarifa + umepokea ujumbe uliobadilishwa, gusa kuendelea na mchakato + %1$s ameondoka kwenye kundi + imefeli kutuma, gusa kwa isiyo salama ili kuanguka nyuma + Haiwezi kupata programu inayoweza kufungua media hii. + nakala 1%s +   Read More +   Download More +   Pending + + Ongeza Kiambatanisho + Chagua taarifa za mawasiliano + Samahani, tatizo lilitokea kwenye kuweka kiambatanisho chako. + Message + Compose + Muted until %1$s + %1$d members + Community Guidelines + Mpokeaji sio sahihi + omeongezwa kwenye skiirini ya mwanzo + toka kwenye kundi + unauhakika unataka kutoka katika kundi hili + kosa la kuondoka kwenye kundi + Fungua mawasiliano hii? + Utapata tena ujumbe na wito kutoka kwa anwani hii. + Fungua + Kiambatisho kimezidi ukubwa wa aina ya ujumbe unaotuma + Haiwezi kurekodi sauti! + hakuna programu ya kuweza kushughulikia kiungo hiki kwenye kifaa chako + Add members + Join %s + Are you sure you want to join the %s open group? + Kutuma ujumbe wa sauti, kuruhusu ufikiaji wa Session kwenye kipaza sauti yako. + Session inahitaji idhini ya Kipaza sauti ili kutuma ujumbe wa sauti, lakini imekataliwa kabisa. Tafadhali endelea kwenye mipangilio ya programu, chagua \"Ruhusa\", na uwawezesha \"Kipaza sauti\". + Ili kukamata picha na video, kuruhusu upatikanaji wa Session kwa kamera. + Session inahitaji kibali cha Kamera kuchukua picha au video, lakini imekataliwa kabisa. Tafadhali endelea kwenye mipangilio ya programu, chagua \"Ruhusa\", na uwawezesha \"Kamera\". + Session inahitaji uruhusu kamera kuchukua picha na video + %1$s %2$s + %1$d of %2$d + No results + + + %d unread message + %d unread messages + + + + Futa ujumbe uliochaguliwa? + Futa + + + This will permanently delete the selected message. + This will permanently delete all %1$d selected messages. + + Ban this user? + Hifadhi kwa kuhifadhi + + Saving this media to storage will allow any other apps on your device to access it.\n\nContinue? + Saving all %1$d media to storage will allow any other apps on your device to access them.\n\nContinue? + + + kosa wakati wa kuhifadhi kiambatisho kwenye stoo + kosa + + + Saving attachment + Saving %1$d attachments + + + Saving attachment to storage... + Saving %1$d attachments to storage... + + Subirisha + Taarifa + Ujumbe wa picha + Ujumbe wa maneno + Kufuta + Kufuta meseji + Banning + Banning user… + Meseji halisi haipatikani + Meseji halisi haipo tena + + Ujumbe wa kubadilishana muhimu + + picha ya wasifu + + tumia desturi: 1%s + Using default: %s + Hakuna + + Sasa + %ddk + Leo + Jana + + Leo + + Faili lisilojulikana + + Hitilafu wakati wa kurejesha GIF kamili ya azimio + + GIFs + Stika + + Photo + + Tap and hold to record a voice message, release to send + + imeshindikana kutafuta ujumbe + Message from %1$s + ujumbe wako + + Vyombo vya habari + + Delete selected message? + Delete selected messages? + + + This will permanently delete the selected message. + This will permanently delete all %1$d selected messages. + + Inafutwa + Kufuta meseji + nyaraka + Chagua vyote + kukusanya viambatisho + + Ujumbe wa Multimedia + kupakua ujumbe wa mms + Hitilafu kupakua ujumbe wa MMS, bomba ili ujaribu tena + + tuma kwa 1%s + + ongeza maelezo + An item was removed because it exceeded the size limit + kamera haipatikani + Message to %s + + You can\'t share more than %d item. + You can\'t share more than %d items. + + + Vyombo vyote vya habari + + Imepokea ujumbe uliofichwa kwa kutumia toleo la zamani la Session ambayo haitumiki tena. Tafadhali kumwomba mtumaji kuwasasishe kwa toleo la hivi karibuni na kurejesha ujumbe. + umetoka kwenye kundi + Umesasisha kikundi + 1%s amesasisha kikundi + + Ukumbe uliotoweka + Ujumbe wako hautopitwa na wakati + Messages sent and received in this conversation will disappear %s after they have been seen. + + Ingiza nenosiri + + Zuia hii anwani? + Hautopokea tena ujumbe na simu kutoka kwa anwani hii + Zuia + Fungua mawasiliano hii? + Utapata tena ujumbe na wito kutoka kwa anwani hii. + Fungua + + picha + Sauti + Video + + kupokea key iliyoharibika badilishana ujumbe + Nimepokea ujumbe wa kubadilishana funguo kwa toleo la protocol sio halali. + ujumbe uliopokelewa pamoja na namba ya usalama. Gusa kuchakata na kuonyesha. + umerekebisha salama kipindi + 1%s kupanga tena kipindi salama + kutoa nakala ya ujumbe + + Kikundi kilichosasishwa + kujitoa katika kundi + rekebisha salama kipindi + rasimu + wewe uliita + ulipigiwa simu + Simu zilizopotea + Ujumbe wa vyombo vya habari + 1%s yupo Session + Uumbe zilizopotea imezuiliwa + Ujumbe unapotea kwa mpangilio wa muda kwa 1%s + %s took a screenshot. + Media saved by %s. + namba salama zimebadilika + namba yako ya usalama pamoja na 1%s imebadilika + umeweka kuthibitishwa? + umeweka haukuthibitishwa + This conversation is empty + Open group invitation + + Session iliyosasishwa + Toleo jipya la Session inapatikana, bomba ili uhakikishe + + ujumbe mbaya encrypted + Message encrypted for non-existing session + + Bad encrypted MMS message + MMS message encrypted for non-existing session + + Arifa za bubu + + gusa kufungua + Session imefunguliwa + zuia Session + + Wewe + aina ya vyombo vya habari ambazo hazijaungwa mkono + rasimu + Session inahitaji ruhusa ya Hifadhi ili kuokoa kwenye hifadhi ya nje, lakini imekataliwa kabisa. Tafadhali endelea kwenye mipangilio ya programu, chagua \"Ruhusa\", na uwezesha \"Uhifadhi\". + haiwezekani kuhifadhi kwenye external storage bila ruhusa + futa ujumbe? + hii itafuta ujumbe kwa kudumu + + 1%1$d ujumbe mpya kwa 2%2$d mazungumzo + ya hivi karibuni kutoka: 1%1$s + ujumbe umefungiwa + utoaji wa ujumbe umefeli + imefeli kutoa ujumbe + hitilafu kupokea ujumbe + nakili yote kama haijasomeka + soma nakili + jibu + Ujumbe wa Session inasubiri + una ujumbe wa Session unasubiri, gusa kufungua na kurudi + %1$s %2$s + Mawasiliano + + Chaguo Msingi + wito + Kushindwa + Rudisha taarifa + Hali ya kufunga + Sasisho za programu + ingine + ujumbe + Isiyojulikana + + Jibu la haraka halipatikani wakati Session imefungwa! + Tatizo kutuma ujumbe + + imehifadhiwa kwa 1%s + imehifadhiwa + + Tafuta + + Mkato wa batili + + Alama + Ujumbe mpya + + + %d Item + %d Items + + + Kasoro kwenye kuchezesha video + + Sauti + Sauti + Mawasiliano + Mawasiliano + Kamera + Kamera + eneo + eneo + GIF + gif + picha au video + jalada + nyumba ya sanaa + jalada + Toggle kiambatisho drawer + + kupakia mawasiliano + + Tuma + utungaji wa ujumbe + Toggle katuni kibodi + kiambatanisho thumbnail + Toggle haraka kamera kiambatisho drawer + Rekodi na tuma kiambatisho cha sauti + Lock recording of audio attachment + Wezesha Session kwa meseji + + Slide to cancel + futa + + Ujumbe wa vyombo vya habari + ujumbe salama + + imefeli kutuma + inasubiri idhini + imefika + soma ujumbe + + wasiliana na picha + + cheza + pumzika + pakua + + Join + Open group invitation + Pinned message + Community guidelines + Read + + Sauti + Video + picha + Wewe + Meseji halisi haipatikani + + sogeza hadi chini + + tafuta GIF na stika + + hakuna kilichopatikana + + Angalia mazungumzo yote + Kupakia + + hakuna vyombo vya habari + + TUMA TENA + + Zuia + + baadhi ya masuala yanahitaji umakini wako + Imetumwa + Imepokelewa + kutoweka + Kupitia + Kuelekea: + Kutoka: + Pamoja: + + kuunda phassphrase + chagua mawasiliano + Uhakiki wa vyombo vya habari + + Tumia chaguo msingi + tumia desturi + nyamaza kwa saa 1 + nyamaza kwa masaa 2 + toa sauti kwa siku moja + toa sauti kwa siku 7 + Simamisha kwa mwaka 1 + Vipimo vya chaguo msingi + wezesha + kuzuia + jina na ujumbe + jina tu + hakuna jina wala ujumbe + picha + Sauti + Video + nyaraka + ndogo + Kawaida + kubwa + kubwa zaidi + Chaguo Msingi + juu + mwisho + + + %d hour + %d hours + + + Ingiza kutuma muhimu + bonyeza kibodi ya kuingia ili kutuma ujumbe wa maandishi + tuma kiungo kilichoonekana + Previews are supported for Imgur, Instagram, Pinterest, Reddit, and YouTube links + usalamaa wa skrini + Zima viwambo vya skrini katika orodha ya rekodi na ndani ya programu + Arifa + Rangi za LED + Isiyojulikana + Mfano wa LED blink + Sauti + Kimya + rudia tahadhari + Haiwezekani + Mara moja + Mara mbili + Mara tatu + Mara tano + Mara kumi + vibrate + Kijani + Nyekundu + Buluu + Njano + cyan + magenta + Nyeupe + Hakuna + Haraka + Kawaida + Polepole + Futa moja kwa moja ujumbe wa zamani mara moja mazungumzo yanazidi urefu uliojulikana + Futa ujumbe wa zamani + mwisho wa urefu wa maongezi + Piga mazungumzo yote sasa + Pitia kupitia mazungumzo yote na uimarishe mipaka ya urefu wa mazungumzo + Chaguo Msingi + Kibodi cha kuingia + Soma stakabadhi + Ikiwa stakabadhi zilizosomwa zimezuiliwa, huwezi kuona stakabadhi zilizosomwa kutoka kwa wengine + andika viashiria + Ikiwa viashiria vya kuandika vimezimwa, huwezi kuona viashiria vya kuandika kutoka kwa wengine. + Omba kibodi ili kuzima kujifunza kwa kibinafsi + Mwanga + Giza + ujumbe unapunguza + kutumia emoji mfumo + kuzuia Session iliyojengwa kusaidia emoji + kutumia programu + mawasiliano + mazungumzo + ujumbe + sauti wakati wa mazungumzo + onyesha + Kipaumbele + + + + + Ujumbe mpya kuelekea.. + + Maelezo ya ujumbe + Nakala ya nakala + Futa ujumbe + Ban user + Tuma tena ujumbe + Jibu ujumbe + + Hifadhi attachment + + Ukumbe uliotoweka + + Ujumbe umepitwa na wakati + + Usifute + + Arifa za bubu + + Hariri kikundi + Toka kwenye kikundi + Vyombo vyote vya habari + Ongeza kwenye skrini ya nyumbani + + Kupanua popup + + utoaji + Mazungumzo + Kutangaza + + Hifadhi + Mbele + Vyombo vyote vya habari + + Hakuna nyaraka + + Uhakiki wa vyombo vya habari + + Kufuta + futa ujumbe wa zamani + Ujumbe wa zamani umefutwa kwa ufanisi + + Ruhusa inahitajika + Endelea + sio sasa + Backups itahifadhiwa kwenye hifadhi ya nje na imetambulishwa na nenosiri chini. Lazima uwe na chapisho hili ili urejeshe upya. + Nimeandika neno hili la kupitisha. Bila hivyo, sitashindwa kurejesha salama. + ruka + Haiwezi kuingiza backups kutoka kwa matoleo mapya ya Session + Sahihi ya kupitisha safu ya salama + kuwezesha salama ya ndani? + Rudisha salama + Tafadhali kukubali ufahamu wako kwa kuashiria sanduku la uthibitisho la kuthibitisha. + Futa vifungo? + Zima na kufuta salama zote za ndani? + Futa salama + Ilikopishwa kwenye ubao wa video + Kujenga salama + 1%d ujumbe hadi sasa + Haiwezekani + safi ya skrini + Funga ufikiaji wa Signal na lock ya Android au alama za vidole + Funga muda wa kuacha kuingia + Hakuna + + Copy public key + + Continue + Copy + Invalid URL + Copied to clipboard + Next + Share + Invalid Session ID + Cancel + Your Session ID + Your Session begins here... + Create Session ID + Continue Your Session + What\'s Session? + It\'s a decentralized, encrypted messaging app + So it doesn\'t collect my personal information or my conversation metadata? How does it work? + Using a combination of advanced anonymous routing and end-to-end encryption technologies. + Friends don\'t let friends use compromised messengers. You\'re welcome. + Say hello to your Session ID + Your Session ID is the unique address people can use to contact you on Session. With no connection to your real identity, your Session ID is totally anonymous and private by design. + Restore your account + Enter the recovery phrase that was given to you when you signed up to restore your account. + Enter your recovery phrase + Pick your display name + This will be your name when you use Session. It can be your real name, an alias, or anything else you like. + Enter a display name + Please pick a display name + Please pick a shorter display name + Recommended + Please Pick an Option + You don\'t have any contacts yet + Start a Session + Are you sure you want to leave this group? + "Couldn't leave group" + Are you sure you want to delete this conversation? + Conversation deleted + Your Recovery Phrase + Meet your recovery phrase + Your recovery phrase is the master key to your Session ID — you can use it to restore your Session ID if you lose access to your device. Store your recovery phrase in a safe place, and don\'t give it to anyone. + Hold to reveal + You\'re almost finished! 80% + Secure your account by saving your recovery phrase + Tap and hold the redacted words to reveal your recovery phrase, then store it safely to secure your Session ID. + Make sure to store your recovery phrase in a safe place + Path + Session hides your IP by bouncing your messages through several Service Nodes in Session\'s decentralized network. These are the countries your connection is currently being bounced through: + You + Entry Node + Service Node + Destination + Learn More + New Session + Enter Session ID + Scan QR Code + Scan a user\'s QR code to start a session. QR codes can be found by tapping the QR code icon in account settings. + Enter Session ID of recipient + Users can share their Session ID by going into their account settings and tapping \"Share Session ID\", or by sharing their QR code. + Session needs camera access to scan QR codes + Grant Camera Access + New Closed Group + Enter a group name + You don\'t have any contacts yet + Start a Session + Please enter a group name + Please enter a shorter group name + Please pick at least 1 group member + A closed group cannot have more than 100 members + Join Open Group + Couldn\'t join group + Open Group URL + Scan QR Code + Scan the QR code of the open group you\'d like to join + Enter an open group URL + Settings + Enter a display name + Please pick a display name + Please pick a shorter display name + Privacy + Notifications + Chats + Devices + Invite + Recovery Phrase + Clear Data + Help us Translate Session + Notifications + Notification Style + Notification Content + Privacy + Chats + Notification Strategy + Change name + Unlink device + Your Recovery Phrase + This is your recovery phrase. With it, you can restore or migrate your Session ID to a new device. + Clear All Data + This will permanently delete your messages, sessions, and contacts. + QR Code + View My QR Code + Scan QR Code + Scan someone\'s QR code to start a conversation with them + Scan Me + This is your QR code. Other users can scan it to start a session with you. + Share QR Code + Contacts + Closed Groups + Open Groups + You don\'t have any contacts yet + + Apply + Done + Edit Group + Enter a new group name + Members + Add members + Group name can\'t be empty + Please enter a shorter group name + Groups must have at least 1 group member + Remove user from group + Select Contacts + Secure session reset done + Theme + Day + Night + System default + Copy Session ID + Attachment + Voice Message + Details + Failed to activate backups. Please try again or contact support. + Restore backup + Select a file + Select a backup file and enter the passphrase it was created with. + 30-digit passphrase + This is taking a while, would you like to skip? + Link a Device + Or join one of these… + Message Notifications + There are two ways Session can notify you of new messages. + Fast Mode + Slow Mode + You’ll be notified of new messages reliably and immediately using Google’s notification servers. + Session will occasionally check for new messages in the background. + Recovery Phrase + Session is Locked + Tap to Unlock + Enter a nickname + Invalid public key +