From 5601da0e22d189d9892ab58a1ddc7f1cefd2316d Mon Sep 17 00:00:00 2001 From: Harris Date: Wed, 15 Dec 2021 17:01:35 +1100 Subject: [PATCH 1/6] fix: replace alpha update with isVisible to not intercept tap from message gestures (#809) --- .../securesms/conversation/v2/ConversationActivityV2.kt | 3 +-- app/src/main/res/layout/activity_conversation_v2.xml | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index be0c82643e..0c4751d805 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -714,7 +714,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } private fun handleRecyclerViewScrolled() { - val alpha = if (!isScrolledToBottom) 1.0f else 0.0f // FIXME: Checking isScrolledToBottom is a quick fix for an issue where the // typing indicator overlays the recycler view when scrolled up val wasTypingIndicatorVisibleBefore = typingIndicatorViewContainer.isVisible @@ -723,7 +722,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe if (isTypingIndicatorVisibleAfter != wasTypingIndicatorVisibleBefore) { inputBarHeightChanged(inputBar.height) } - scrollToBottomButton.alpha = alpha + scrollToBottomButton.isVisible = !isScrolledToBottom unreadCount = min(unreadCount, layoutManager.findFirstVisibleItemPosition()) updateUnreadCountIndicator() } diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index 079c932e99..61e1680ef6 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -94,8 +94,7 @@ android:layout_alignParentRight="true" android:layout_alignParentBottom="true" android:layout_marginRight="12dp" - android:layout_marginBottom="72dp" - android:alpha="1"> + android:layout_marginBottom="72dp"> Date: Wed, 15 Dec 2021 08:11:55 +0200 Subject: [PATCH 2/6] feat: Update open group avatars periodically (#807) * feat: Update open group avatars periodically * Updated timestamp * Existing job check * Refresh avatar on the conversation * Remove println statement * Update profile picture on recipient modified event --- .../conversation/v2/ConversationActivityV2.kt | 1 + .../securesms/database/GroupDatabase.java | 18 ++++++- .../securesms/database/SessionJobDatabase.kt | 17 ++++-- .../securesms/database/Storage.kt | 9 ++++ .../database/helpers/SQLCipherOpenHelper.java | 8 ++- .../securesms/groups/OpenGroupManager.kt | 14 +---- .../libsession/database/StorageProtocol.kt | 3 ++ .../messaging/jobs/GroupAvatarDownloadJob.kt | 54 +++++++++++++++++++ .../libsession/messaging/jobs/JobQueue.kt | 16 ++++-- .../jobs/SessionJobManagerFactories.kt | 3 +- .../pollers/OpenGroupPollerV2.kt | 14 ++++- .../libsession/utilities/GroupRecord.kt | 5 +- 12 files changed, 135 insertions(+), 27 deletions(-) create mode 100644 libsession/src/main/java/org/session/libsession/messaging/jobs/GroupAvatarDownloadJob.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt index 0c4751d805..267403ff14 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationActivityV2.kt @@ -501,6 +501,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } updateSubtitle() showOrHideInputIfNeeded() + profilePictureView.update(recipient, threadID) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java index 78cc9b405d..a9042ed399 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupDatabase.java @@ -53,6 +53,7 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt private static final String TIMESTAMP = "timestamp"; private static final String ACTIVE = "active"; private static final String MMS = "mms"; + private static final String UPDATED = "updated"; // Loki private static final String AVATAR_URL = "avatar_url"; @@ -83,11 +84,16 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt private static final String[] GROUP_PROJECTION = { GROUP_ID, TITLE, MEMBERS, ZOMBIE_MEMBERS, AVATAR, AVATAR_ID, AVATAR_KEY, AVATAR_CONTENT_TYPE, AVATAR_RELAY, AVATAR_DIGEST, - TIMESTAMP, ACTIVE, MMS, AVATAR_URL, ADMINS + TIMESTAMP, ACTIVE, MMS, AVATAR_URL, ADMINS, UPDATED }; static final List TYPED_GROUP_PROJECTION = Stream.of(GROUP_PROJECTION).map(columnName -> TABLE_NAME + "." + columnName).toList(); + public static String getCreateUpdatedTimestampCommand() { + return "ALTER TABLE "+ TABLE_NAME + " " + + "ADD COLUMN " + UPDATED + " INTEGER DEFAULT 0;"; + } + public GroupDatabase(Context context, SQLCipherOpenHelper databaseHelper) { super(context, databaseHelper); } @@ -330,6 +336,13 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", new String[] {groupId}); } + public void updateTimestampUpdated(String groupId, Long updatedTimestamp) { + ContentValues contents = new ContentValues(); + contents.put(UPDATED, updatedTimestamp); + + databaseHelper.getWritableDatabase().update(TABLE_NAME, contents, GROUP_ID + " = ?", new String[] {groupId}); + } + public void removeMember(String groupId, Address source) { List
currentMembers = getCurrentMembers(groupId, false); currentMembers.remove(source); @@ -439,7 +452,8 @@ public class GroupDatabase extends Database implements LokiOpenGroupDatabaseProt cursor.getInt(cursor.getColumnIndexOrThrow(MMS)) == 1, cursor.getString(cursor.getColumnIndexOrThrow(AVATAR_URL)), cursor.getString(cursor.getColumnIndexOrThrow(ADMINS)), - cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP))); + cursor.getLong(cursor.getColumnIndexOrThrow(TIMESTAMP)), + cursor.getLong(cursor.getColumnIndexOrThrow(UPDATED))); } @Override diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt index 49729241a5..95d7e5e3d4 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/SessionJobDatabase.kt @@ -3,13 +3,17 @@ package org.thoughtcrime.securesms.database import android.content.ContentValues import android.content.Context import net.sqlcipher.Cursor -import org.session.libsession.messaging.jobs.* +import org.session.libsession.messaging.jobs.AttachmentUploadJob +import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob +import org.session.libsession.messaging.jobs.Job +import org.session.libsession.messaging.jobs.MessageReceiveJob +import org.session.libsession.messaging.jobs.MessageSendJob +import org.session.libsession.messaging.jobs.SessionJobInstantiator +import org.session.libsession.messaging.jobs.SessionJobManagerFactories import org.session.libsession.messaging.utilities.Data import org.session.libsignal.utilities.Log -import org.thoughtcrime.securesms.database.* import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper import org.thoughtcrime.securesms.jobmanager.impl.JsonDataSerializer -import org.thoughtcrime.securesms.util.* class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Database(context, helper) { @@ -78,6 +82,13 @@ class SessionJobDatabase(context: Context, helper: SQLCipherOpenHelper) : Databa } } + fun getGroupAvatarDownloadJob(server: String, room: String): GroupAvatarDownloadJob? { + val database = databaseHelper.readableDatabase + return database.getAll(sessionJobTable, "$jobType = ?", arrayOf(GroupAvatarDownloadJob.KEY)) { + jobFromCursor(it) as GroupAvatarDownloadJob? + }.filterNotNull().find { it.server == server && it.room == room } + } + fun cancelPendingMessageSendJobs(threadID: Long) { val database = databaseHelper.writableDatabase val attachmentUploadJobKeys = mutableListOf() 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 196bffcdb6..1dfc1f0b25 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -185,6 +185,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return DatabaseComponent.get(context).sessionJobDatabase().getMessageReceiveJob(messageReceiveJobID) } + override fun getGroupAvatarDownloadJob(server: String, room: String): GroupAvatarDownloadJob? { + return DatabaseComponent.get(context).sessionJobDatabase().getGroupAvatarDownloadJob(server, room) + } + override fun resumeMessageSendJobIfNeeded(messageSendJobID: String) { val job = DatabaseComponent.get(context).sessionJobDatabase().getMessageSendJob(messageSendJobID) ?: return JobQueue.shared.resumePendingSendMessage(job) @@ -468,6 +472,11 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, .updateFormationTimestamp(groupID, formationTimestamp) } + override fun updateTimestampUpdated(groupID: String, updatedTimestamp: Long) { + DatabaseComponent.get(context).groupDatabase() + .updateTimestampUpdated(groupID, updatedTimestamp) + } + override fun setExpirationTimer(groupID: String, duration: Int) { val recipient = Recipient.from(context, fromSerialized(groupID), false) DatabaseComponent.get(context).recipientDatabase().setExpireMessages(recipient, duration); diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java index e563e6e5c3..b0a02041ac 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/helpers/SQLCipherOpenHelper.java @@ -61,9 +61,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { private static final int lokiV27 = 48; private static final int lokiV28 = 49; private static final int lokiV29 = 50; + private static final int lokiV30 = 51; // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes - private static final int DATABASE_VERSION = lokiV29; + private static final int DATABASE_VERSION = lokiV30; private static final String DATABASE_NAME = "signal.db"; private final Context context; @@ -136,6 +137,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(SessionContactDatabase.getCreateSessionContactTableCommand()); db.execSQL(RecipientDatabase.getCreateNotificationTypeCommand()); db.execSQL(ThreadDatabase.getCreatePinnedCommand()); + db.execSQL(GroupDatabase.getCreateUpdatedTimestampCommand()); executeStatements(db, SmsDatabase.CREATE_INDEXS); executeStatements(db, MmsDatabase.CREATE_INDEXS); @@ -314,6 +316,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper { db.execSQL(ThreadDatabase.getCreatePinnedCommand()); } + if (oldVersion < lokiV30) { + db.execSQL(GroupDatabase.getCreateUpdatedTimestampCommand()); + } + db.setTransactionSuccessful(); } finally { db.endTransaction(); diff --git a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt index 3545e2fd2a..e18f1a8e8f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/groups/OpenGroupManager.kt @@ -1,7 +1,6 @@ package org.thoughtcrime.securesms.groups import android.content.Context -import android.graphics.Bitmap import androidx.annotation.WorkerThread import okhttp3.HttpUrl import org.session.libsession.messaging.MessagingModuleConfiguration @@ -72,18 +71,9 @@ object OpenGroupManager { OpenGroupAPIV2.getAuthToken(room, server).get() // Get group info val info = OpenGroupAPIV2.getInfo(room, server).get() - // Download the group image - // FIXME: Don't wait for the image to download - val image: Bitmap? + // Create the group locally if not available already if (threadID < 0) { - val profilePictureAsByteArray = try { - OpenGroupAPIV2.downloadOpenGroupProfilePicture(info.id, server).get() - } catch (e: Exception) { - null - } - image = BitmapUtil.fromByteArray(profilePictureAsByteArray) - // Create the group locally - threadID = GroupManager.createOpenGroup(openGroupID, context, image, info.name).threadId + threadID = GroupManager.createOpenGroup(openGroupID, context, null, info.name).threadId } val openGroup = OpenGroupV2(server, room, info.name, publicKey) threadDB.setOpenGroupChat(openGroup, threadID) 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 09a3ba5e4c..c983f524a6 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -4,6 +4,7 @@ import android.content.Context import android.net.Uri import org.session.libsession.messaging.contacts.Contact import org.session.libsession.messaging.jobs.AttachmentUploadJob +import org.session.libsession.messaging.jobs.GroupAvatarDownloadJob import org.session.libsession.messaging.jobs.Job import org.session.libsession.messaging.jobs.MessageSendJob import org.session.libsession.messaging.messages.control.ConfigurationMessage @@ -43,6 +44,7 @@ interface StorageProtocol { fun getAttachmentUploadJob(attachmentID: Long): AttachmentUploadJob? fun getMessageSendJob(messageSendJobID: String): MessageSendJob? fun getMessageReceiveJob(messageReceiveJobID: String): Job? + fun getGroupAvatarDownloadJob(server: String, room: String): Job? fun resumeMessageSendJobIfNeeded(messageSendJobID: String) fun isJobCanceled(job: Job): Boolean @@ -117,6 +119,7 @@ interface StorageProtocol { fun getClosedGroupEncryptionKeyPairs(groupPublicKey: String): MutableList fun getLatestClosedGroupEncryptionKeyPair(groupPublicKey: String): ECKeyPair? fun updateFormationTimestamp(groupID: String, formationTimestamp: Long) + fun updateTimestampUpdated(groupID: String, updatedTimestamp: Long) fun setExpirationTimer(groupID: String, duration: Int) // Groups diff --git a/libsession/src/main/java/org/session/libsession/messaging/jobs/GroupAvatarDownloadJob.kt b/libsession/src/main/java/org/session/libsession/messaging/jobs/GroupAvatarDownloadJob.kt new file mode 100644 index 0000000000..227944a889 --- /dev/null +++ b/libsession/src/main/java/org/session/libsession/messaging/jobs/GroupAvatarDownloadJob.kt @@ -0,0 +1,54 @@ +package org.session.libsession.messaging.jobs + +import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 +import org.session.libsession.messaging.utilities.Data +import org.session.libsession.utilities.GroupUtil + +class GroupAvatarDownloadJob(val room: String, val server: String) : Job { + + override var delegate: JobDelegate? = null + override var id: String? = null + override var failureCount: Int = 0 + override val maxFailureCount: Int = 10 + + override fun execute() { + val storage = MessagingModuleConfiguration.shared.storage + try { + val info = OpenGroupAPIV2.getInfo(room, server).get() + val bytes = OpenGroupAPIV2.downloadOpenGroupProfilePicture(info.id, server).get() + val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray()) + storage.updateProfilePicture(groupId, bytes) + storage.updateTimestampUpdated(groupId, System.currentTimeMillis()) + delegate?.handleJobSucceeded(this) + } catch (e: Exception) { + delegate?.handleJobFailed(this, e) + } + } + + override fun serialize(): Data { + return Data.Builder() + .putString(ROOM, room) + .putString(SERVER, server) + .build() + } + + override fun getFactoryKey(): String = KEY + + companion object { + const val KEY = "GroupAvatarDownloadJob" + + private const val ROOM = "room" + private const val SERVER = "server" + } + + class Factory : Job.Factory { + + override fun create(data: Data): GroupAvatarDownloadJob { + return GroupAvatarDownloadJob( + data.getString(ROOM), + data.getString(SERVER) + ) + } + } +} \ No newline at end of file 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 88510fb278..016fb27e05 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 @@ -45,9 +45,16 @@ class JobQueue : JobDelegate { while (isActive) { for (job in queue) { when (job) { - is NotifyPNServerJob, is AttachmentUploadJob, is MessageSendJob -> txQueue.send(job) - is MessageReceiveJob, is TrimThreadJob, is BatchMessageReceiveJob, is AttachmentDownloadJob-> rxQueue.send(job) - else -> throw IllegalStateException("Unexpected job type.") + is NotifyPNServerJob, is AttachmentUploadJob, is MessageSendJob -> { + txQueue.send(job) + } + is MessageReceiveJob, is TrimThreadJob, is BatchMessageReceiveJob, + is AttachmentDownloadJob, is GroupAvatarDownloadJob -> { + rxQueue.send(job) + } + else -> { + throw IllegalStateException("Unexpected job type.") + } } } } @@ -123,7 +130,8 @@ class JobQueue : JobDelegate { MessageReceiveJob.KEY, MessageSendJob.KEY, NotifyPNServerJob.KEY, - BatchMessageReceiveJob.KEY + BatchMessageReceiveJob.KEY, + GroupAvatarDownloadJob.KEY ) allJobTypes.forEach { type -> resumePendingJobs(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 cc22c59dbf..9a3a97401c 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 @@ -12,7 +12,8 @@ class SessionJobManagerFactories { MessageSendJob.KEY to MessageSendJob.Factory(), NotifyPNServerJob.KEY to NotifyPNServerJob.Factory(), TrimThreadJob.KEY to TrimThreadJob.Factory(), - BatchMessageReceiveJob.KEY to BatchMessageReceiveJob.Factory() + BatchMessageReceiveJob.KEY to BatchMessageReceiveJob.Factory(), + GroupAvatarDownloadJob.KEY to GroupAvatarDownloadJob.Factory() ) } } 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 194072e543..c604fb7f9b 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 @@ -40,6 +40,7 @@ class OpenGroupPollerV2(private val server: String, private val executorService: fun poll(isBackgroundPoll: Boolean = false): Promise { val storage = MessagingModuleConfiguration.shared.storage val rooms = storage.getAllV2OpenGroups().values.filter { it.server == server }.map { it.room } + rooms.forEach { downloadGroupAvatarIfNeeded(it) } return OpenGroupAPIV2.compactPoll(rooms, server).successBackground { responses -> responses.forEach { (room, response) -> val openGroupID = "$server.$room" @@ -50,7 +51,7 @@ class OpenGroupPollerV2(private val server: String, private val executorService: } } }.always { - executorService?.schedule(this@OpenGroupPollerV2::poll, OpenGroupPollerV2.pollInterval, TimeUnit.MILLISECONDS) + executorService?.schedule(this@OpenGroupPollerV2::poll, pollInterval, TimeUnit.MILLISECONDS) }.map { } } @@ -103,4 +104,15 @@ class OpenGroupPollerV2(private val server: String, private val executorService: storage.setLastDeletionServerID(room, server, latestMax) } } + + private fun downloadGroupAvatarIfNeeded(room: String) { + val storage = MessagingModuleConfiguration.shared.storage + if (storage.getGroupAvatarDownloadJob(server, room) != null) return + val groupId = GroupUtil.getEncodedOpenGroupID("$server.$room".toByteArray()) + storage.getGroup(groupId)?.let { + if (System.currentTimeMillis() > it.updatedTimestamp + TimeUnit.DAYS.toMillis(7)) { + JobQueue.shared.add(GroupAvatarDownloadJob(room, server)) + } + } + } } \ No newline at end of file diff --git a/libsession/src/main/java/org/session/libsession/utilities/GroupRecord.kt b/libsession/src/main/java/org/session/libsession/utilities/GroupRecord.kt index 348b6bcae9..630e6a89ef 100644 --- a/libsession/src/main/java/org/session/libsession/utilities/GroupRecord.kt +++ b/libsession/src/main/java/org/session/libsession/utilities/GroupRecord.kt @@ -1,15 +1,14 @@ package org.session.libsession.utilities import android.text.TextUtils -import org.session.libsession.utilities.Address import java.io.IOException -import java.util.* +import java.util.LinkedList class GroupRecord( val encodedId: String, val title: String, members: String?, val avatar: ByteArray?, val avatarId: Long?, val avatarKey: ByteArray?, val avatarContentType: String?, val relay: String?, val isActive: Boolean, val avatarDigest: ByteArray?, val isMms: Boolean, - val url: String?, admins: String?, val formationTimestamp: Long + val url: String?, admins: String?, val formationTimestamp: Long, val updatedTimestamp: Long ) { var members: List
= LinkedList
() var admins: List
= LinkedList
() From 28b97e45cc896201f9265abbda72f63f5775cfec Mon Sep 17 00:00:00 2001 From: Harris Date: Thu, 16 Dec 2021 15:47:26 +1100 Subject: [PATCH 3/6] Remove ID copy interactions (#811) * fix: remove selected session ID interactions * fix: include thread ID in home activity's creation of user details bottom sheet * refactor: use bundleOf builder instead of old bundle put value methods --- .../menus/ConversationActionModeCallback.kt | 2 +- .../v2/menus/ConversationMenuHelper.kt | 1 - .../v2/messages/VisibleMessageView.kt | 14 +++++++++---- .../securesms/home/HomeActivity.kt | 7 +++++-- .../securesms/home/UserDetailsBottomSheet.kt | 20 ++++++++++++++++++- 5 files changed, 35 insertions(+), 9 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt index d5eb26b479..275691c002 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationActionModeCallback.kt @@ -67,7 +67,7 @@ class ConversationActionModeCallback(private val adapter: ConversationAdapter, p menu.findItem(R.id.menu_context_copy).isVisible = !containsControlMessage && hasText // Copy Session ID menu.findItem(R.id.menu_context_copy_public_key).isVisible = - (thread.isGroupRecipient && selectedItems.size == 1 && firstMessage.recipient.address.toString() != userPublicKey) + (thread.isGroupRecipient && !thread.isOpenGroupRecipient && selectedItems.size == 1 && firstMessage.recipient.address.toString() != userPublicKey) // Message detail menu.findItem(R.id.menu_message_details).isVisible = (selectedItems.size == 1 && firstMessage.isFailed) // Resend diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt index 8a33f985c6..adfbf297e6 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/menus/ConversationMenuHelper.kt @@ -77,7 +77,6 @@ object ConversationMenuHelper { } else { inflater.inflate(R.menu.menu_conversation_block, menu) } - inflater.inflate(R.menu.menu_conversation_copy_session_id, menu) } // Closed group menu (options that should only be present in closed groups) if (thread.isClosedGroupRecipient) { diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt index d9cd334bc8..802409cbe7 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageView.kt @@ -16,6 +16,7 @@ import android.widget.RelativeLayout import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat import androidx.core.content.res.ResourcesCompat +import androidx.core.os.bundleOf import androidx.core.view.isVisible import dagger.hilt.android.AndroidEntryPoint import kotlinx.android.synthetic.main.view_visible_message.view.* @@ -23,6 +24,7 @@ import network.loki.messenger.R import org.session.libsession.messaging.contacts.Contact.ContactContext import org.session.libsession.messaging.open_groups.OpenGroupAPIV2 import org.session.libsession.utilities.ViewUtil +import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.utilities.ThreadUtils import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.* @@ -103,7 +105,9 @@ class VisibleMessageView : LinearLayout { profilePictureView.publicKey = senderSessionID profilePictureView.glide = glide profilePictureView.update(message.individualRecipient, threadID) - profilePictureView.setOnClickListener { showUserDetails(message.recipient.address.toString()) } + profilePictureView.setOnClickListener { + showUserDetails(senderSessionID, threadID) + } if (thread.isOpenGroupRecipient) { val openGroup = lokiThreadDb.getOpenGroupChat(threadID) ?: return val isModerator = OpenGroupAPIV2.isUserModerator(senderSessionID, openGroup.room, openGroup.server) @@ -379,10 +383,12 @@ class VisibleMessageView : LinearLayout { pressCallback = null } - private fun showUserDetails(publicKey: String) { + private fun showUserDetails(publicKey: String, threadID: Long) { val userDetailsBottomSheet = UserDetailsBottomSheet() - val bundle = Bundle() - bundle.putString("publicKey", publicKey) + val bundle = bundleOf( + UserDetailsBottomSheet.ARGUMENT_PUBLIC_KEY to publicKey, + UserDetailsBottomSheet.ARGUMENT_THREAD_ID to threadID + ) userDetailsBottomSheet.arguments = bundle val activity = context as AppCompatActivity userDetailsBottomSheet.show(activity.supportFragmentManager, userDetailsBottomSheet.tag) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index e0fad535ef..6373aaca1f 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -12,6 +12,7 @@ import android.text.SpannableString import android.text.style.ForegroundColorSpan import android.view.View import android.widget.Toast +import androidx.core.os.bundleOf import androidx.core.view.isVisible import androidx.lifecycle.Observer import androidx.lifecycle.lifecycleScope @@ -250,8 +251,10 @@ class HomeActivity : PassphraseRequiredActionBarActivity(), ConversationClickLis bottomSheet.onViewDetailsTapped = { bottomSheet.dismiss() val userDetailsBottomSheet = UserDetailsBottomSheet() - val bundle = Bundle() - bundle.putString("publicKey", thread.recipient.address.toString()) + val bundle = bundleOf( + UserDetailsBottomSheet.ARGUMENT_PUBLIC_KEY to thread.recipient.address.toString(), + UserDetailsBottomSheet.ARGUMENT_THREAD_ID to thread.threadId + ) userDetailsBottomSheet.arguments = bundle userDetailsBottomSheet.show(supportFragmentManager, userDetailsBottomSheet.tag) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt index 43a9b432d5..35785492d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/UserDetailsBottomSheet.kt @@ -12,7 +12,10 @@ import android.view.ViewGroup import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager import android.widget.Toast +import androidx.core.view.isVisible import com.google.android.material.bottomsheet.BottomSheetDialogFragment +import dagger.hilt.EntryPoint +import dagger.hilt.android.AndroidEntryPoint import kotlinx.android.synthetic.main.fragment_user_details_bottom_sheet.* import network.loki.messenger.R import org.session.libsession.messaging.MessagingModuleConfiguration @@ -20,20 +23,32 @@ import org.session.libsession.messaging.contacts.Contact import org.session.libsession.utilities.Address import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 +import org.thoughtcrime.securesms.database.ThreadDatabase import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.mms.GlideApp import org.thoughtcrime.securesms.util.UiModeUtilities +import javax.inject.Inject +@AndroidEntryPoint class UserDetailsBottomSheet : BottomSheetDialogFragment() { + @Inject lateinit var threadDb: ThreadDatabase + + companion object { + const val ARGUMENT_PUBLIC_KEY = "publicKey" + const val ARGUMENT_THREAD_ID = "threadId" + } + override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? { return inflater.inflate(R.layout.fragment_user_details_bottom_sheet, container, false) } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) - val publicKey = arguments?.getString("publicKey") ?: return dismiss() + val publicKey = arguments?.getString(ARGUMENT_PUBLIC_KEY) ?: return dismiss() + val threadID = arguments?.getLong(ARGUMENT_THREAD_ID) ?: return dismiss() val recipient = Recipient.from(requireContext(), Address.fromSerialized(publicKey), false) + val threadRecipient = threadDb.getRecipientForThreadId(threadID) ?: return dismiss() profilePictureView.publicKey = publicKey profilePictureView.glide = GlideApp.with(this) profilePictureView.isLarge = true @@ -65,6 +80,9 @@ class UserDetailsBottomSheet : BottomSheetDialogFragment() { } } nameTextView.text = recipient.name ?: publicKey // Uses the Contact API internally + + publicKeyTextView.isVisible = !threadRecipient.isOpenGroupRecipient + messageButton.isVisible = !threadRecipient.isOpenGroupRecipient publicKeyTextView.text = publicKey publicKeyTextView.setOnLongClickListener { val clipboard = requireContext().getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager From 164937aaa305ddfdf9f7ea33782c68f10cc4712d Mon Sep 17 00:00:00 2001 From: Harris Date: Fri, 17 Dec 2021 10:19:32 +1100 Subject: [PATCH 4/6] Remove the read and typing indication on startup (#812) * fix: remove the read and typing indication * fix: set default value in preferences_app_protection.xml --- .../org/thoughtcrime/securesms/onboarding/LandingActivity.kt | 2 -- app/src/main/res/xml/preferences_app_protection.xml | 2 +- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/onboarding/LandingActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/onboarding/LandingActivity.kt index 3ca0632482..4fbd0f6885 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/onboarding/LandingActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/onboarding/LandingActivity.kt @@ -23,8 +23,6 @@ class LandingActivity : BaseActionBarActivity() { findViewById(R.id.linkButton).setOnClickListener { link() } IdentityKeyUtil.generateIdentityKeyPair(this) TextSecurePreferences.setPasswordDisabled(this, true) - TextSecurePreferences.setReadReceiptsEnabled(this, true) - TextSecurePreferences.setTypingIndicatorsEnabled(this, true) // AC: This is a temporary workaround to trick the old code that the screen is unlocked. KeyCachingService.setMasterSecret(applicationContext, Object()) } diff --git a/app/src/main/res/xml/preferences_app_protection.xml b/app/src/main/res/xml/preferences_app_protection.xml index 35f9028a5b..691278183b 100644 --- a/app/src/main/res/xml/preferences_app_protection.xml +++ b/app/src/main/res/xml/preferences_app_protection.xml @@ -70,7 +70,7 @@ android:summary="@string/preferences__if_read_receipts_are_disabled_you_wont_be_able_to_see_read_receipts"/> From 87045b1d55bb860beeda8271e36ca240727e4b86 Mon Sep 17 00:00:00 2001 From: jubb Date: Fri, 17 Dec 2021 11:03:16 +1100 Subject: [PATCH 5/6] build: update build number and versio nname --- app/build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index 87347f013a..c32fff90fb 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -154,8 +154,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 235 -def canonicalVersionName = "1.11.13" +def canonicalVersionCode = 242 +def canonicalVersionName = "1.11.14" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, From 8ea0690e3a0f69a9350245628f058daf8d367027 Mon Sep 17 00:00:00 2001 From: jubb Date: Fri, 17 Dec 2021 15:07:53 +1100 Subject: [PATCH 6/6] fix: remove sh-rHR strings due to localisation error --- app/src/main/res/values-sh-rHR/strings.xml | 718 --------------------- 1 file changed, 718 deletions(-) delete mode 100644 app/src/main/res/values-sh-rHR/strings.xml diff --git a/app/src/main/res/values-sh-rHR/strings.xml b/app/src/main/res/values-sh-rHR/strings.xml deleted file mode 100644 index f3b7aee68b..0000000000 --- a/app/src/main/res/values-sh-rHR/strings.xml +++ /dev/null @@ -1,718 +0,0 @@ - - - mECC - Da - Ne - Obriši - Banuj - Sačekaj... - Spremi - Osobna bilješka - Verzija %s - - Nova poruka - - \+%d - - - %d poruka po razgovoru - %d poruke po razgovoru - %d poruka po razgovoru - %d poruka po razgovoru - - Obriši sve stare poruke? - - Ovo će automatski skratiti sve razgovore na odabrani broj poruka. - Ovo će automatski skratiti sve razgovore na %d najnovije poruke. - Ovo će automatski skratiti sve razgovore na %d najnovijih poruka. - Ovo će automatski skratiti sve razgovore na %d najnovijih poruka. - - Obriši - Uključeno - Isključeno - - (slika) - (zvuk) - (video) - (odgovor) - - Nije moguće pronaći aplikaciju za odabir medija. - mECC zahtijeva dozvolu pristupa Pohrani podataka za umetanje slikovnih i audio-vizualnih priloga ali zahtjev biva odbijen. Molim otvorite opcije aplikacije, odaberite \"Dozvole\" i uključite \"Pohrana\". - mECC zahtijeva dozvolu pristupa Kontaktima za prilaganje informacije o kontaktima ali pristup biva odbijen. Molim otvorite opcije aplikacije, odaberite \"Dozvole\" i uključite \"Kontakti\". - mECC zahtijeva dozvolu pristupa Kameri za omogućavanje slikanja ali pristup biva odbijen. Molim otvorite opcije aplikacije, odaberite \"Dozvole\" i uključite \"Kamera\". - - Greška prilikom reprodukcije zvuka! - - Danas - Juče - Ova sedmica - Ovaj mjesec - - Web pretraživač nije pronađen. - - Grupe - - Slanje nije uspjelo, dodirni za detalje - Poruka za razmjenu ključeva je primljena, pritisni da nastaviš. - %1$s je napustio grupu. - Slanje neuspješno, pritisni za nesigurnu rezervu - Nije moguće pronaći aplikaciju za otvaranje ovog medija. - Kopirano %s - Preuzmi više - Na čekanju - - Dodaj prilog - Odaberite informacije kontakta - Došlo je do greške prilikom postavljanja priloga. - Poruka - Sastavi - Prigušen do %1$s - %1$d Dodaj članove - Pravila zajednice - Neispravan primatelj! - Dodano na početni ekran - Napusti grupu? - Jesi li siguran da želiš napustiti ovu grupu? - Greška pri napuštanju grupe - Ukloni blokadu ovog kontakta? - Ponovno ćeš moći primati poruke ili pozive ovog korisnika. - Ukloni blokadu - Prilog prelazi ograničenje veličine za tip poruke koju šalješ. - Nije moguće snimati svuk! - Na tvom uređaju ne postoji aplikacija koja bi rukovala ovim linkom. - Dodaj članove - Pridruži se %s - Jeste li sigurni da se želite pridružiti otvorenoj grupi %s? - Kako biste poslali audio poruku, dozvolite mECC-u pristup vašem mikrofonu. - mECC zahtijeva dozvolu pristupa Mikrofonu za slanje zvučnih poruka ali pristup biva odbijen. Molim otvorite opcije aplikacije, odaberite \"Dozvole\" i uključite \"Mikrofon\". - Kako biste snimili slike i video, dozvolite mECC-u pristup kameri. - mECC zahtijeva dozvolu Kameri za fotografiranje ili snimanje videa, ali pristup biva odbijen. Molim nastavite s postavkama aplikacije, odaberite \"Dozvole\" i omogućite \"Kamera\". - mECC treba pristup kameri kako bi snimio slike ili video. - %1$s%2$s - %1$d od %2$d - Nema rezultata - - - %d nepročitana poruka - %d nepročitane poruke - %d nepročitanih poruka - %d nepročitanih poruka - - - - Obriši odabrane poruke? - Obriši odabrane poruke? - Obriši odabrane poruke? - Obriši odabrane poruke? - - - Ovo će trajno obrisati odabrane poruke. - Ovo će trajno obrisati sve %1$d odabrane poruke. - Ovo će trajno obrisati svih %1$d odabranih poruka. - Ovo će trajno obrisati svih %1$d odabranih poruka. - - Banovati ovog korisnika? - Spremi na disk? - - Spremanje svih medija na disk će omogućiti pristup mediju iz drugih aplikacija na vašem uređaju.\n\nNastavi? - Spremanje sva %1$d medija na disk će omogućiti pristup mediju iz drugih aplikacija na vašem uređaju.\n\nNastavi? - Spremanje svih %1$d medija na disk će omogućiti pristup mediju iz drugih aplikacija na vašem uređaju.\n\nNastavi? - Spremanje svih %1$d medija na disk će omogućiti pristup mediju iz drugih aplikacija na vašem uređaju.\n\nNastavi? - - - Greška prilikom spremanja privitaka na disk! - Greška prilikom spremanja privitaka na disk! - Greška prilikom spremanja privitaka na disk! - Greška prilikom spremanja priloga na disk! - - - Spremanje priloga - Spremanje %1$d priloga - Spremanje %1$d priloga - Spremanje %1$d priloga - - - Spremanje prologa na disk... - Spremanje %1$d priloga na disk... - Spremanje %1$d priloga na disk... - Spremanje %1$d priloga na disk... - - U toku... - Podaci (mECC) - MMS - SMS - Brisanje - Brisanje poruka... - Banovanje - Banovanje korisnika… - Originalna poruka nije pronađena - Originalna poruka više nije dostupna - - Poruka za razmjenu ključa - - Profilna slika - - Koristeći prilagođeno: %s - Koristeći zadano: %s - Niti jedna - - Sada - %d min - Danas - Juče - - Danas - - Nepoznata datoteka - - Greška pri dohvaćanju GIFa pune rezolucije - - GIFovi - Naljepnice - - Slika profila - - Pritisni i drži kako bi snimio glasovnu poruku, pusti za slanje - - Nije moguće naći poruku - Poruka od %1$s - Tvoja poruka - - Medij - - Izbriši označenu poruku? - Izbriši označene poruke? - Izbriši označene poruke? - Izbriši označene poruke? - - - Ovo će trajno obrisati označenu poruku. - Broj poruka koji će biti trajno izbrisan: %1$d - Broj poruka koji će biti trajno izbrisan: %1$d - Broj poruka koji će biti trajno izbrisan: %1$d - - Brisanje - Brisanje poruka... - Dokumenti - Odaberi sve - Prikupljanje priloga... - - Multimedijalna poruka - Preuzimanje MMS poruke - Greška pri preuzimanju MMS poruke, pritisni za ponovni pokušaj - - Pošalji u %s - - Dodaj naslov... - Stavka je uklonjena jer je prešla limit veličine - Kamera nije dostupna - Poruka prema %s - - Ne možeš dijeliti više od %dstavke. - Ne možeš dijeliti više od %d stavki. - Ne možeš dijeliti više od %dstavki. - Ne možeš dijeliti više od %dstavki. - - - Svi mediji - - Primljena je poruka kriptirana starom verzijom mECC aplikacije koja više nije podržana. Natjeraj Ba pošiljatelja da ažurira na najnoviju verziju aplikacije i ponovno pošalje poruku. - Napustio si grupu. - Ažurao si grupu. - %s je ažurirao grupu. - - Nestajuće poruke - Tvoje poruke neće isteći. - Primljene i poslane poruke u ovom razgovoru će nestati %s nakon što su viđene. - - Unesi lozinku - - Blokiraj kontakt? - Nećeš više primati poruke i pozive ovog korisnika. - Blokiraj - Ukloni blokadu ovog kontakta? - Ponovno ćete moći primati poruke ili pozive ovog korisnika. - Ukloni blokadu - - Slika - Audio - Video - - Primljena iskvarena poruka -razmjene ključeva! - Primljena poruka razmjene ključeva za pogrešnu veryiju protokola. - Primljena je poruka s novim sigurnosnim brojem. Pritisni za obradu i prikaz. - Resetiranje sigurne sesije mECC-a. - %s resetiranje sigurne sesije mECC-a. - Dupla poruka. - - Grupa je ažurirana - Napustio/la grupu - Resetiranje sigurne sesije eMCC-a. - Skica: - Zvao si - Zvao te - Propušteni poziv - Multimedijalna poruka - %s je dostupan na mECC-u! - Poruke koje nestaju onemogućene - Vrijeme nestajanja poruke postavljeno na %s - %s je napravio snimak zaslona. - %s je sačuvao mediju. - Sigurnosni broj je izmijenjen - Vaš sigurnosni broj s %s je izmjenjen. - Označio si provjerenim - Označio si nepotvrđenim - Ovaj razgovor je prazan - Otvori pozivnicu za grupu - - mECC ažuriranje - Nova verza mECC-a je dostupna, pritisni za ažuriranje - - Loše kriptirana poruka - Poruka kriptirana za nepostojeću sesiju - - Loše kriptirana MMS poruka - MMS poruka kriptirana za nepostojeću sesiju - - Utišaj obavijesti - - Pritisni za otvaranje. - mECC je otključan - Zaključaj mECC - - Ti - Nepodržani tip medija - Skica - Nije moguće spremanje na vanjski medij bez dozvola - Obriši poruku? - Radnja će trajno izbrisati ovu poruku. - - %1$d novih poruka u %2$d razgovora - Najnovije od: %1$s - Zaključana poruka - Neuspješna isporuka poruke. - Isporuka poruke nije uspjela. - Greška prilikom isporuke poruke. - Označi sve kao pročitano - Označi pročitano - Odgovori - mECC poruke na čekanju - Imaš mECC poruka na čekanju, pritisni kako bi ih otvorio i preuzeo - %1$s%2$s - Kontakt - - Zadano - Pozivi - Neuspjesi - Sigurnosne kopije - Status zaključavanja - Ažuriranja aplikacije - Ostalo - Poruke - Nepoznato - - Brzi odgovor nije dostupan kada je mECC zaključan! - Greška prilikom slanja poruke! - - Spremljeno u %s - Sačuvano - - Traži - - Nevažeći prečac - - mECC - Nova poruka - - - Greška pri video reprodukciji - - Zvuk - Zvuk - Kontakt - Kontakt - Kamera - Kamera - Položaj - Položaj - GIF - Gif - Slika ili video - Datoteka - Galerija - Datoteka - Uključi/isključi ladicu dodataka - - Učitavanje kontakata... - - Pošalji - Sastavljanje poruke - Uključi/isključi emoji tipkovnicu - Priložena sličica - Uključi/isključi ladicu brze kamere - Snimi i pošalji audio privitak - Zaključaj snimanje audio priloga - Omogući mECC za SMS - - Klizni za otkazivanje - Odustani - - Multimedijalna poruka - Sigurna poruka - - Neuspješno slanje - Odobrenje u tijeku - Isporučeno - Poruka pročitana - - Slika kontakta - - Reprodukcija - Zaustavi - Preuzmi - - Pridruži se - Otvori pozivnicu za grupu - Prikačena poruka - Pravila zajednice - Pročitaj - - Zvuk - Video - Fotografija - Vi - Originalna poruka nije pronađena - - Kliži do dna - - Pretraži GIFove i naljepnice - - Ništa nije pronađeno. - - Prikaži cijeli razgovor - Učitavanje - - Bez medija - - PONOVNO POŠALJI - - Blokiraj - - Neki problemi zahtjevaju tvoju pozornost. - Poslano - Primljeno - Nestaje - Putem - Prima: - Šalje: - Sa: - - Stvori lozinku - Odaberite kontakte - Prikaz medija - - Koristi zadano - Koristi prilagođeno - Utišaj na 1 sat - Utišaj na 2 sata - Utišaj na 1 dan - Utišaj na 7 dana - Utišaj na 1 godinu - Zadana podešavanja - Omogućeno - Onemogućeno - Ime i poruka - Samo ime - Nema imena ili poruke - Slike - Audio - Video - Dokumenti - Mala - Normalna - Velika - Ekstra velika - Zadano - Visoko - Maks - - - %d sat - %d sata - %d sati - %d sati - - - Enter šalje poruku - Pritisak na Enter će poslati tekst poruku - Pošalji preglede linkova - Pregledi su podržani za linkove Imgur, Instagram, Pinterest, Reddit i YouTube - Sigurnost ekrana - Onemogući snimanje ekrana na popisu nedavnih i unutar aplikacije - Obavijesti - LED boja - Nepoznato - LED uzorak treptanja - Zvuk - Bezvučno - Ponovi upozorenja - Nikada - Jednom - Dva puta - Tri puta - Pet puta - Deset puta - Vibracija - Zelena - Crvena - Plava - Narančasta - Cijan - Magenta - Bijela - Niti jedna - Brzo - Normalno - Sporo - Automatski obriši starije poruke nakon što razgovor pređe određenu duljinu - Obriši stare poruke - Maksimalna duljina razgovora - Skrati sve razgovore odmah - Skeniraj sve razgovore i primijeni ograničenje duljine razgovora - Zadano - Inkognito tipkovnica - Potvrde čitanja - Ukoliko si onemogućio potvrdu čitanja kod sebe, nećeš moći vidjeti potvrde čitanja od drugih. - Pokazatelji tipkanja - Ako su indikatori kucanja onemogućeni, nećeš moći vidjeti indikatore kucanja od drugih. - Zahtijevaj od tipkovnice da onemogući personalizirano učenje - Svijetla - Tamna - Skraćivanje poruke - Koristi emotikone sustava - Onemogući ugrađenu mECC podršku za emotikone - Pristup aplikaciji - Komunikacija - Razgovori - Poruke - Zvuk unutar razgovora - Prikaži - Prioritet - - - - - Nova poruka za... - - Detalji poruke - Kopiraj tekst - Obriši poruku - Banuj korisnika - Ponovno pošalji poruku - Odgovori na poruku - - Spremi privitak - - Nestajuće poruke - - Poruke ističu - - Ukloni utišanje - - Utišaj obavijesti - - Uredi grupu - Napusti grupu - Svi mediji - Dodaj na početni ekran - - Proširi skočni prozor - - Isporuka - Razgovor - Emitiranje - - Spremi - Prosljedi - Svi mediji - - Nema dokumenata - - Prikaz medija - - Brisanje - Brisanje starih poruka... - Stare poruke su uspješno obrisane - - Potrebna dozvola - Nastavi - Ne sada - Sigurnosne kopije bit će spremljene u vanjsku pohranu i šifrirane donjom šifrom. Moraš imati ovu šifru da bi vratio sigurnosnu kopiju. - Zapisao sam ovu pristupnu frazu. Bez toga neću moći da vratim rezervnu kopiju. - Preskoči - Ne mogu da se uvezu rezervne kopije iz novijih verzija mECC - Pogrešna šifra za sigurnosnu kopiju - Omogućiti lokalne sigurnosne kopije? - Omogući sigurnosne kopije - Potvrdi svoje razumijevanje označavanjem polja za potvrdu. - Izbrisati sigurnosne kopije? - Onemogućiti i izbrisati sve lokalne sigurnosne kopije? - Izbriši sigurnosne kopije - Kopirano u međuspremnik - Stvaranje sigurnosne kopije... - %d poruka do sada - Nikada - Zaključavanje ekrana - Zaključaj pristup mECC-u pomoću Android zaključavanja ekrana ili otiska prsta - Vrijeme neaktivnosti za zaključavanja ekrana - Niti jedna - - Kopiraj javni ključ - - Nastavi - Kopiraj - Nevažeći URL - Kopirano u međuspremnik - Sledeći - Podjeli - Nevažeci mECC ID - Otkaži - Tvoj mECC ID - Tvoj mECC počinje ovdje... - Napravi mECC ID - Nastavi svoj mECC - Šta je mECC? - Decentralizirana, enkriptovana aplikacija za razmjenu poruka - Dakle, ne prikuplja moje lične podatke ili metapodatke razgovora? Kako to radi? - Koristeći kombinaciju naprednog anonimnog usmjeravanja i end-to-end tehnologije šifriranja. (Ne pitaj puno) - Prijatelji ne dopuštaju prijateljima da koriste kompromitirane aplikacije. Nema na čemu. - Pozdravi svoj mECC ID - Tvoj mECC ID je jedinstvena adresa koju ljudi mogu koristiti da te kontaktiraju na mECC-u. Bez poveznice sa tvojim stvarnim identitetom, tvoj mECC ID je potpuno anoniman i dizajniran da bude privatan. - Vrati svoj račun - Unesi frazu za oporavak koja ti je dana kada si se registrovao prvi put da bi vratio račun. - Unesite frazu za oporavak - Izaberi svoj nadimak - Ovo će biti tvoje ime kada budeš koristili mECC. To može biti vaše pravo ime (Nije preporučljivo), pseudonim ili bilo šta drugo što želiš. - Unesi nadimak - Odaberi nadimak - Odaberi kraći nadimak - Preporučeno - Odaberi jednu opciu - Još nemaš nijedan kontakt - Pokreni mECC - Jesi li siguran da želiš napustiti ovu grupu? - "Napuštanje grupe nije uspjelo" - Jesi li siguran da želiš izbrisati ovaj razgovor? - Razgovor je izbrisan - Tvoja fraza za oporavak - Upoznajte svoju frazu za oporavak - Tvoja fraza za oporavak glavni je ključ tvog mECC ID-a - možeš ga koristiti za vraćanje ID-a mECC-a ako izgubiš pristup uređaju. Spremi frazu za oporavak na sigurno mjesto i ne daj je nikome. - Drži da otkriješ - Skoro si gotov! 80% - Osigurajte svoj račun spremanjem fraze za oporavak - Dodirni i zadrži uređene riječi da bi otkrio frazu za oporavak, a zatim je sigurno spremi kako biste osigurao svoj mECC ID. - Obavezno pohranite frazu za oporavak na sigurno mjesto (Mojne da ti to padne u pogrešne ruke) - Putanja - mECC sakriva tvoj IP odbijanjem vaših poruka kroz nekoliko servisnih node-ova u decentraliziranoj mreži mCCA-a. Ne boj se jarane valja ovo. Ovo su zemlje kroz koje se tvoja veza trenutno odbija: - Ti - Ulazni Node - Servisni Node - Destinacija - Naučiti više - Novi mECC - Ukucaj mECC ID - Skeniraj QR Kod - Skeniraj QR kod korisnika da biste započeli link. QR kodovi se mogu pronaći dodirom ikone QR koda u podešavanjima računa. - Korisnici mogu podijeliti svoj mECC ID tako što će otići u postavke svog računa i dodirnuti \"Podjeli mECC ID\" ili dijeljenjem svog QR koda. - mECC treba pristup kamere za skeniranje QR kodova - Odobri pristup kameri - Nova zatvorena grupa - Unesi naziv grupe - Još nemaš nijedan kontakt - Pokreni mECC - Unesi naziv grupe - Unesi kraće ime grupe - Odaberi najmanje 1 člana grupe - Zatvorena grupa ne može imati više od 100 članova - Pridruži se otvorenoj grupi - Pridruživanje grupi nije uspjelo - Otvori URL Grupe - Skeniraj QR Kod - Skeniraj QR kod otvorene grupe kojoj se želiš pridružiti - Unesi URL otvorene grupe - Podešavanja - Unesite mECC nadimak - Odaberi mECC nadimak - Odaberi kraći mECC nadimak - Privatnost - Notifikacije - Razgovori - Uređaji - Fraza za oporavak - Obriši podatke - UPDATE - Notifikacije - Stil Notifikacija - Sadržaj notifikacija - Privatnost - Razgovori - Strategija Notifikacija - Promjeni ime - Odveži uređaj - Tvoja fraza za oporavak - Ovo je tvoja fraza za oporavak. Pomoću nje možeš vratiti ili migrirati svoj mECC ID na novi uređaj. - Obriši sve Podatke - Ovo će trajno izbrisati tvoje poruke, mECC ID-e i kontakte. - QR Kod - Prikaži moj QR Kod - Skeniraj QR Kod - Skeniraj nečiji QR kod da započneš razgovor s njim - Skeniraj ME - Ovo je tvoj QR kod. Drugi korisnici mogu da ga skeniraju kako bi započeli mECC link s tobom. - Podjeli QR Kod - Kontakti - Zatvorene Grupe - Otvorene Grupe - Još nemaš nijedan kontakt - - Primjeni - Završeno - Uredi grupu - Unesi novi naziv grupe - Članovi - Dodaj članove - Ime grupe ne može biti prazno - Unesi kraće ime grupe - Grupe moraju imati najmanje 1 člana - Ukloni korisnika iz grupe - Odaberi kontakte - Sigurnosno resetiranje mECC-a obavljeno - Tema - Dan - Noć - Sistemski zadana podešavanja - Kopiraj vaš session ID - Prilog - Glasovna Poruka - Detalji - Aktiviranje sigurnosnih kopija nije uspjelo. Pokušaj ponovo ili kontaktirajte administratora za podršku. - Vrati sigurnosnu kopiju - Odaberi datoteku - Odaberi datoteku sigurnosne kopije i unesi šifru pomoću koje je kreirana. - 30-cifrena šifra - Ovo traje neko vrijeme, želiš li preskočiti? - Poveži uređaj - Ili se pridruži jednom od ovih… - Obavijesti o porukama - Postoje dva načina na koje te mECC može obavijestiti o novim porukama. - Brzi režim - Spori režim - Obavjesti za nove poruke dobit ćeš pouzdano i odmah pomoću nekih jebenih servera za obavijesti. - mECC će povremeno provjeravati ima li novih poruka u pozadini. - Fraza za oporavak - mECC je zaključan - Dodirni za otključavanje - Unesi nadimak - Nevažeći javni ključ -