diff --git a/app/build.gradle b/app/build.gradle index a85f0478d3..64a7e9c9b7 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -156,8 +156,8 @@ dependencies { testImplementation 'org.robolectric:shadows-multidex:4.4' } -def canonicalVersionCode = 299 -def canonicalVersionName = "1.15.3" +def canonicalVersionCode = 302 +def canonicalVersionName = "1.15.4" def postFixSize = 10 def abiPostFix = ['armeabi-v7a' : 1, 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 467be2c0b6..e5815d974f 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 @@ -1397,7 +1397,13 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } else it.individualRecipient.address QuoteModel(it.dateSent, sender, it.body, false, quotedAttachments) } - val outgoingTextMessage = OutgoingMediaMessage.from(message, recipient, attachments, quote, linkPreview) + val localQuote = quotedMessage?.let { + val sender = + if (it.isOutgoing) fromSerialized(textSecurePreferences.getLocalNumber()!!) + else it.individualRecipient.address + quote?.copy(author = sender) + } + val outgoingTextMessage = OutgoingMediaMessage.from(message, recipient, attachments, localQuote, linkPreview) // Clear the input bar binding?.inputBar?.text = "" binding?.inputBar?.cancelQuoteDraft() @@ -1713,7 +1719,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe override fun resendMessage(messages: Set) { messages.iterator().forEach { messageRecord -> - ResendMessageUtilities.resend(messageRecord) + ResendMessageUtilities.resend(this, messageRecord, viewModel.blindedPublicKey) } endActionMode() } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt index 72274ef7c7..4d78653abc 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt @@ -11,6 +11,7 @@ import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.session.libsession.messaging.open_groups.OpenGroup +import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.utilities.recipients.Recipient @@ -41,7 +42,7 @@ class ConversationViewModel( get() = openGroup?.let { storage.getServerCapabilities(it.server) } ?: listOf() val blindedPublicKey: String? - get() = if (openGroup == null || edKeyPair == null) null else { + get() = if (openGroup == null || edKeyPair == null || !serverCapabilities.contains(OpenGroupApi.Capability.BLIND.name.lowercase())) null else { SodiumUtilities.blindedKeyPair(openGroup!!.publicKey, edKeyPair)?.publicKey?.asBytes ?.let { SessionId(IdPrefix.BLINDED, it) }?.hexString } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt index 72757ce25d..27f75701d9 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/MessageDetailActivity.kt @@ -2,24 +2,36 @@ package org.thoughtcrime.securesms.conversation.v2 import android.os.Bundle import android.view.View +import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ActivityMessageDetailBinding +import org.session.libsession.messaging.MessagingModuleConfiguration +import org.session.libsession.messaging.open_groups.OpenGroupApi +import org.session.libsession.messaging.utilities.SessionId +import org.session.libsession.messaging.utilities.SodiumUtilities import org.session.libsession.utilities.Address import org.session.libsession.utilities.ExpirationUtil import org.session.libsession.utilities.TextSecurePreferences +import org.session.libsignal.utilities.IdPrefix import org.thoughtcrime.securesms.PassphraseRequiredActionBarActivity import org.thoughtcrime.securesms.conversation.v2.utilities.ResendMessageUtilities +import org.thoughtcrime.securesms.database.Storage import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.util.DateUtils import java.text.SimpleDateFormat import java.util.Date import java.util.Locale +import javax.inject.Inject +@AndroidEntryPoint class MessageDetailActivity: PassphraseRequiredActionBarActivity() { private lateinit var binding: ActivityMessageDetailBinding var messageRecord: MessageRecord? = null + @Inject + lateinit var storage: Storage + // region Settings companion object { // Extras @@ -37,9 +49,19 @@ class MessageDetailActivity: PassphraseRequiredActionBarActivity() { // so the author of the messages must be the current user. val author = Address.fromSerialized(TextSecurePreferences.getLocalNumber(this)!!) messageRecord = DatabaseComponent.get(this).mmsSmsDatabase().getMessageFor(timestamp, author) + val threadId = messageRecord!!.threadId + val openGroup = storage.getOpenGroup(threadId) + val blindedKey = openGroup?.let { group -> + val userEdKeyPair = MessagingModuleConfiguration.shared.getUserED25519KeyPair() ?: return@let null + val blindingEnabled = storage.getServerCapabilities(group.server).contains(OpenGroupApi.Capability.BLIND.name.lowercase()) + if (blindingEnabled) { + SodiumUtilities.blindedKeyPair(group.publicKey, userEdKeyPair)?.publicKey?.asBytes + ?.let { SessionId(IdPrefix.BLINDED, it) }?.hexString + } else null + } updateContent() binding.resendButton.setOnClickListener { - ResendMessageUtilities.resend(messageRecord!!) + ResendMessageUtilities.resend(this, messageRecord!!, blindedKey) finish() } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt index f98fccd150..a08bef5a55 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/QuoteView.kt @@ -13,6 +13,7 @@ import dagger.hilt.android.AndroidEntryPoint import network.loki.messenger.R import network.loki.messenger.databinding.ViewQuoteBinding import org.session.libsession.messaging.contacts.Contact +import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities import org.thoughtcrime.securesms.database.SessionContactDatabase @@ -69,7 +70,12 @@ class QuoteView @JvmOverloads constructor(context: Context, attrs: AttributeSet? isOriginalMissing: Boolean, glide: GlideRequests) { // Author val author = contactDb.getContactWithSessionID(authorPublicKey) - val authorDisplayName = author?.displayName(Contact.contextForRecipient(thread)) ?: "${authorPublicKey.take(4)}...${authorPublicKey.takeLast(4)}" + val localNumber = TextSecurePreferences.getLocalNumber(context) + val quoteIsLocalUser = localNumber != null && localNumber == author?.sessionID + + val authorDisplayName = + if (quoteIsLocalUser) context.getString(R.string.QuoteView_you) + else author?.displayName(Contact.contextForRecipient(thread)) ?: "${authorPublicKey.take(4)}...${authorPublicKey.takeLast(4)}" binding.quoteViewAuthorTextView.text = authorDisplayName binding.quoteViewAuthorTextView.setTextColor(getTextColor(isOutgoingMessage)) // Body diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt index 62004808a0..80f4cc0bf8 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ResendMessageUtilities.kt @@ -1,5 +1,6 @@ package org.thoughtcrime.securesms.conversation.v2.utilities +import android.content.Context import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.messages.visible.LinkPreview import org.session.libsession.messaging.messages.visible.OpenGroupInvitation @@ -7,13 +8,14 @@ import org.session.libsession.messaging.messages.visible.Quote import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.utilities.UpdateMessageData +import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord object ResendMessageUtilities { - fun resend(messageRecord: MessageRecord) { + fun resend(context: Context, messageRecord: MessageRecord, userBlindedKey: String?) { val recipient: Recipient = messageRecord.recipient val message = VisibleMessage() message.id = messageRecord.getId() @@ -44,6 +46,9 @@ object ResendMessageUtilities { } if (mmsMessageRecord.quote != null) { message.quote = Quote.from(mmsMessageRecord.quote!!.quoteModel) + if (userBlindedKey != null && messageRecord.quote!!.author.serialize() == TextSecurePreferences.getLocalNumber(context)) { + message.quote!!.publicKey = userBlindedKey + } } message.addSignalAttachments(mmsMessageRecord.slideDeck.asAttachments()) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/GroupMemberDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/GroupMemberDatabase.kt index ce2e2eba8d..ff44ef2c9a 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/GroupMemberDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/GroupMemberDatabase.kt @@ -51,31 +51,31 @@ class GroupMemberDatabase(context: Context, helper: SQLCipherOpenHelper) : Datab return mappings.map { it.role } } - fun addGroupMember(member: GroupMember) { + fun setGroupMembers(members: List) { writableDatabase.beginTransaction() try { - val values = ContentValues().apply { - put(GROUP_ID, member.groupId) - put(PROFILE_ID, member.profileId) - put(ROLE, member.role.name) - } - val query = "$GROUP_ID = ? AND $PROFILE_ID = ?" - val args = arrayOf(member.groupId, member.profileId) + val grouped = members.groupBy { it.role } + grouped.forEach { (role, members) -> + if (members.isEmpty()) return@forEach - writableDatabase.insertOrUpdate(TABLE_NAME, values, query, args) - writableDatabase.setTransactionSuccessful() - } finally { - writableDatabase.endTransaction() - } - } + val toDeleteQuery = "$GROUP_ID = ? AND $ROLE = ?" + val toDeleteArgs = arrayOf(members.first().groupId, role.name) - fun clearGroupMemberRoles(groupId: String) { - writableDatabase.beginTransaction() - try { - val query = "$GROUP_ID = ?" - val args = arrayOf(groupId) - writableDatabase.delete(TABLE_NAME, query, args) - writableDatabase.setTransactionSuccessful() + writableDatabase.delete(TABLE_NAME, toDeleteQuery, toDeleteArgs) + + members.forEach { member -> + val values = ContentValues().apply { + put(GROUP_ID, member.groupId) + put(PROFILE_ID, member.profileId) + put(ROLE, member.role.name) + } + val query = "$GROUP_ID = ? AND $PROFILE_ID = ?" + val args = arrayOf(member.groupId, member.profileId) + + writableDatabase.insertOrUpdate(TABLE_NAME, values, query, args) + } + writableDatabase.setTransactionSuccessful() + } } finally { writableDatabase.endTransaction() } 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 14116e9e4b..508cde51a3 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -319,12 +319,8 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, return getAllOpenGroups().values.firstOrNull { it.server == server && it.room == room } } - override fun clearGroupMemberRoles(groupId: String) { - DatabaseComponent.get(context).groupMemberDatabase().clearGroupMemberRoles(groupId) - } - - override fun addGroupMemberRole(member: GroupMember) { - DatabaseComponent.get(context).groupMemberDatabase().addGroupMember(member) + override fun setGroupMemberRoles(members: List) { + DatabaseComponent.get(context).groupMemberDatabase().setGroupMembers(members) } override fun isDuplicateMessage(timestamp: Long): Boolean { diff --git a/app/src/main/res/layout/view_visible_message_content.xml b/app/src/main/res/layout/view_visible_message_content.xml index 5084571371..edd5e5cbfa 100644 --- a/app/src/main/res/layout/view_visible_message_content.xml +++ b/app/src/main/res/layout/view_visible_message_content.xml @@ -104,7 +104,7 @@ android:visibility="gone" app:layout_constraintTop_toBottomOf="@+id/bodyTopBarrier" app:layout_constraintStart_toStartOf="parent" - android:maxWidth="300dp" + android:maxWidth="@dimen/max_text_width" android:paddingHorizontal="12dp" android:paddingVertical="@dimen/small_spacing" android:id="@+id/bodyTextView" diff --git a/app/src/main/res/values-sw360dp/dimens.xml b/app/src/main/res/values-sw360dp/dimens.xml index c361548af8..65d2c52883 100644 --- a/app/src/main/res/values-sw360dp/dimens.xml +++ b/app/src/main/res/values-sw360dp/dimens.xml @@ -11,4 +11,5 @@ 210dp 125dp 83dp + 240dp \ No newline at end of file diff --git a/app/src/main/res/values-sw400dp/dimens.xml b/app/src/main/res/values-sw400dp/dimens.xml index 07440848c8..59ee98e861 100644 --- a/app/src/main/res/values-sw400dp/dimens.xml +++ b/app/src/main/res/values-sw400dp/dimens.xml @@ -19,4 +19,5 @@ 250dp 149dp 99dp + 300dp \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml index 87134eb0b2..40f94551ae 100644 --- a/app/src/main/res/values/dimens.xml +++ b/app/src/main/res/values/dimens.xml @@ -145,5 +145,6 @@ 390dp 40dp + 200dp 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 fb1aac3d22..46c412d967 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -71,8 +71,7 @@ interface StorageProtocol { fun hasBackgroundGroupAddJob(groupJoinUrl: String): Boolean fun setOpenGroupServerMessageID(messageID: Long, serverID: Long, threadID: Long, isSms: Boolean) fun getOpenGroup(room: String, server: String): OpenGroup? - fun addGroupMemberRole(member: GroupMember) - fun clearGroupMemberRoles(groupId: String) + fun setGroupMemberRoles(members: List) // Open Group Public Keys fun getOpenGroupPublicKey(server: String): String? 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 1322da58d2..86b856d952 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 @@ -231,12 +231,18 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, throw MessageReceiver.Error.NoThread } val threadRecipient = storage.getRecipientForThread(threadID) + val userBlindedKey = openGroupID?.let { + val openGroup = storage.getOpenGroup(threadID) ?: return@let null + val blindedKey = SodiumUtilities.blindedKeyPair(openGroup.publicKey, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) ?: return@let null + SessionId( + IdPrefix.BLINDED, blindedKey.publicKey.asBytes + ).hexString + } // Update profile if needed val recipient = Recipient.from(context, Address.fromSerialized(messageSender!!), false) if (runProfileUpdate) { val profile = message.profile - val isUserBlindedSender = messageSender == storage.getOpenGroup(threadID)?.publicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId( - IdPrefix.BLINDED, it.publicKey.asBytes).hexString } + val isUserBlindedSender = messageSender == userBlindedKey if (profile != null && userPublicKey != messageSender && !isUserBlindedSender) { val profileManager = SSKEnvironment.shared.profileManager val name = profile.displayName!! @@ -260,7 +266,13 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage, var quoteModel: QuoteModel? = null if (message.quote != null && proto.dataMessage.hasQuote()) { val quote = proto.dataMessage.quote - val author = Address.fromSerialized(quote.author) + + val author = if (quote.author == userBlindedKey) { + Address.fromSerialized(userPublicKey!!) + } else { + Address.fromSerialized(quote.author) + } + val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider val messageInfo = messageDataProvider.getMessageForQuote(quote.id, author) quoteModel = if (messageInfo != null) { diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt index 7bb00f8f35..145155e97c 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/pollers/OpenGroupPoller.kt @@ -134,20 +134,26 @@ class OpenGroupPoller(private val server: String, private val executorService: S storage.setUserCount(roomToken, server, pollInfo.activeUsers) // - Moderators - storage.clearGroupMemberRoles(groupId) - - pollInfo.details?.moderators?.forEach { - storage.addGroupMemberRole(GroupMember(groupId, it, GroupMemberRole.MODERATOR)) + pollInfo.details?.moderators?.let { moderatorList -> + storage.setGroupMemberRoles(moderatorList.map { + GroupMember(groupId, it, GroupMemberRole.MODERATOR) + }) } - pollInfo.details?.hiddenModerators?.forEach { - storage.addGroupMemberRole(GroupMember(groupId, it, GroupMemberRole.HIDDEN_MODERATOR)) + pollInfo.details?.hiddenModerators?.let { moderatorList -> + storage.setGroupMemberRoles(moderatorList.map { + GroupMember(groupId, it, GroupMemberRole.HIDDEN_MODERATOR) + }) } // - Admins - pollInfo.details?.admins?.forEach { - storage.addGroupMemberRole(GroupMember(groupId, it, GroupMemberRole.ADMIN)) + pollInfo.details?.admins?.let { moderatorList -> + storage.setGroupMemberRoles(moderatorList.map { + GroupMember(groupId, it, GroupMemberRole.ADMIN) + }) } - pollInfo.details?.hiddenAdmins?.forEach { - storage.addGroupMemberRole(GroupMember(groupId, it, GroupMemberRole.HIDDEN_ADMIN)) + pollInfo.details?.hiddenAdmins?.let { moderatorList -> + storage.setGroupMemberRoles(moderatorList.map { + GroupMember(groupId, it, GroupMemberRole.HIDDEN_ADMIN) + }) } } diff --git a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/quotes/QuoteModel.kt b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/quotes/QuoteModel.kt index 0764b35c32..b1c0fb9cc2 100644 --- a/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/quotes/QuoteModel.kt +++ b/libsession/src/main/java/org/session/libsession/messaging/sending_receiving/quotes/QuoteModel.kt @@ -3,7 +3,7 @@ package org.session.libsession.messaging.sending_receiving.quotes import org.session.libsession.messaging.sending_receiving.attachments.Attachment import org.session.libsession.utilities.Address -class QuoteModel(val id: Long, +data class QuoteModel(val id: Long, val author: Address, val text: String?, val missing: Boolean,