From c458d4a3599303c0710e32b8d2648425a5248c84 Mon Sep 17 00:00:00 2001 From: Harris Date: Thu, 10 Mar 2022 09:54:18 +1100 Subject: [PATCH] Fix approved references and visible message setting flags for approval (#854) * fix: set approved on new outgoing threads, use approved more deeply and invalidate the options menu on recipient modified. Add approvedMe flag toggles for visible message receive * fix: add name update in action bar on modified, change where approvedMe is set * fix: text colours and attachment approve requests * refactor: text color for share screen * Restart conversation loader on message request approval Co-authored-by: ceokot --- .../conversation/v2/ConversationActivityV2.kt | 70 +++++++++++-------- .../conversation/v2/ConversationViewModel.kt | 4 ++ .../conversation/v2/messages/QuoteView.kt | 7 +- .../v2/messages/VisibleMessageContentView.kt | 2 +- .../securesms/database/RecipientDatabase.java | 9 ++- .../securesms/database/Storage.kt | 42 +++++++++-- .../repository/ConversationRepository.kt | 5 ++ .../layout/contact_selection_list_divider.xml | 2 +- app/src/main/res/layout/view_input_bar.xml | 1 + .../main/res/layout/view_visible_message.xml | 1 + .../libsession/database/StorageProtocol.kt | 3 +- 11 files changed, 100 insertions(+), 46 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 a693e954f..50dd13499 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 @@ -151,7 +151,7 @@ import kotlin.math.sqrt class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDelegate, InputBarRecordingViewDelegate, AttachmentManager.AttachmentListener, ActivityDispatcher, ConversationActionModeCallbackDelegate, VisibleMessageContentViewDelegate, RecipientModifiedListener, - SearchBottomBar.EventListener, VoiceMessageViewDelegate { + SearchBottomBar.EventListener, VoiceMessageViewDelegate, LoaderManager.LoaderCallbacks { private var binding: ActivityConversationV2Binding? = null private var actionBarBinding: ActivityConversationV2ActionBarBinding? = null @@ -350,34 +350,32 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe baseDialog.show(supportFragmentManager, tag) } + override fun onCreateLoader(id: Int, bundle: Bundle?): Loader { + return ConversationLoader(viewModel.threadId, !isIncomingMessageRequestThread(), this@ConversationActivityV2) + } + + override fun onLoadFinished(loader: Loader, cursor: Cursor?) { + adapter.changeCursor(cursor) + if (cursor != null) { + val messageTimestamp = messageToScrollTimestamp.getAndSet(-1) + val author = messageToScrollAuthor.getAndSet(null) + if (author != null && messageTimestamp >= 0) { + jumpToMessage(author, messageTimestamp, null) + } + } + } + + override fun onLoaderReset(cursor: Loader) { + adapter.changeCursor(null) + } + // called from onCreate private fun setUpRecyclerView() { binding!!.conversationRecyclerView.adapter = adapter - val reverseLayout = !isIncomingMessageRequestThread() - val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, reverseLayout) + val layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, !isIncomingMessageRequestThread()) binding!!.conversationRecyclerView.layoutManager = layoutManager // Workaround for the fact that CursorRecyclerViewAdapter doesn't auto-update automatically (even though it says it will) - LoaderManager.getInstance(this).restartLoader(0, null, object : LoaderManager.LoaderCallbacks { - - override fun onCreateLoader(id: Int, bundle: Bundle?): Loader { - return ConversationLoader(viewModel.threadId, reverseLayout, this@ConversationActivityV2) - } - - override fun onLoadFinished(loader: Loader, cursor: Cursor?) { - adapter.changeCursor(cursor) - if (cursor != null) { - val messageTimestamp = messageToScrollTimestamp.getAndSet(-1) - val author = messageToScrollAuthor.getAndSet(null) - if (author != null && messageTimestamp >= 0) { - jumpToMessage(author, messageTimestamp, null) - } - } - } - - override fun onLoaderReset(cursor: Loader) { - adapter.changeCursor(null) - } - }) + LoaderManager.getInstance(this).restartLoader(0, null, this) binding!!.conversationRecyclerView.addOnScrollListener(object : RecyclerView.OnScrollListener() { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { @@ -388,7 +386,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe // called from onCreate private fun setUpToolBar() { - val actionBar = supportActionBar!! + val actionBar = supportActionBar ?: return actionBarBinding = ActivityConversationV2ActionBarBinding.inflate(layoutInflater) actionBar.title = "" actionBar.customView = actionBarBinding!!.root @@ -581,9 +579,11 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe if (viewModel.recipient.isContactRecipient) { binding?.blockedBanner?.isVisible = viewModel.recipient.isBlocked } + invalidateOptionsMenu() updateSubtitle() showOrHideInputIfNeeded() actionBarBinding?.profilePictureView?.update(recipient) + actionBarBinding?.conversationTitleView?.text = recipient.toShortString() } } @@ -616,16 +616,16 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe binding?.messageRequestBar?.isVisible = false binding?.conversationRecyclerView?.layoutManager = LinearLayoutManager(this, LinearLayoutManager.VERTICAL, true) + adapter.notifyDataSetChanged() viewModel.acceptMessageRequest() + LoaderManager.getInstance(this).restartLoader(0, null, this) lifecycleScope.launch(Dispatchers.IO) { ConfigurationMessageUtilities.forceSyncConfigurationNowIfNeeded(this@ConversationActivityV2) } } private fun isMessageRequestThread(): Boolean { - val hasSent = threadDb.getLastSeenAndHasSent(viewModel.threadId).second() - return (!viewModel.recipient.isGroupRecipient && !hasSent) || - (!viewModel.recipient.isGroupRecipient && hasSent && !(viewModel.recipient.hasApprovedMe() || viewModel.hasReceived())) + return !viewModel.recipient.isGroupRecipient && !viewModel.recipient.isApproved } private fun isOutgoingMessageRequestThread(): Boolean { @@ -999,9 +999,6 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } override fun sendMessage() { - if (isIncomingMessageRequestThread()) { - acceptMessageRequest() - } if (viewModel.recipient.isContactRecipient && viewModel.recipient.isBlocked) { BlockedDialog(viewModel.recipient).show(supportFragmentManager, "Blocked Dialog") return @@ -1019,7 +1016,17 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe startActivityForResult(MediaSendActivity.buildEditorIntent(this, listOf( media ), viewModel.recipient, getMessageBody()), PICK_FROM_LIBRARY) } + private fun processMessageRequestApproval() { + if (isIncomingMessageRequestThread()) { + acceptMessageRequest() + } else if (!viewModel.recipient.isApproved) { + // edge case for new outgoing thread on new recipient without sending approval messages + viewModel.setRecipientApproved() + } + } + private fun sendTextOnlyMessage(hasPermissionToSendSeed: Boolean = false) { + processMessageRequestApproval() val text = getMessageBody() val userPublicKey = textSecurePreferences.getLocalNumber() val isNoteToSelf = (viewModel.recipient.isContactRecipient && viewModel.recipient.address.toString() == userPublicKey) @@ -1049,6 +1056,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } private fun sendAttachments(attachments: List, body: String?, quotedMessage: MessageRecord? = null, linkPreview: LinkPreview? = null) { + processMessageRequestApproval() // Create the message val message = VisibleMessage() message.sentTimestamp = System.currentTimeMillis() 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 dbe35ee96..89cfc6db5 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 @@ -53,6 +53,10 @@ class ConversationViewModel( repository.deleteLocally(recipient, message) } + fun setRecipientApproved() { + repository.setApproved(recipient, true) + } + fun deleteForEveryone(message: MessageRecord) = viewModelScope.launch { repository.deleteForEveryone(threadId, recipient, message) .onFailure { 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 3df12a516..95d76dd94 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 @@ -19,7 +19,6 @@ import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities import org.thoughtcrime.securesms.conversation.v2.utilities.TextUtilities import org.thoughtcrime.securesms.database.SessionContactDatabase import org.thoughtcrime.securesms.database.model.Quote -import org.thoughtcrime.securesms.database.model.ThreadRecord import org.thoughtcrime.securesms.mms.GlideRequests import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.util.MediaUtil @@ -184,10 +183,10 @@ class QuoteView : LinearLayout { @ColorInt private fun getTextColor(isOutgoingMessage: Boolean): Int { if (mode == Mode.Draft) { return ResourcesCompat.getColor(resources, R.color.text, context.theme) } val isLightMode = UiModeUtilities.isDayUiMode(context) - return if ((isOutgoingMessage && !isLightMode) || (!isOutgoingMessage && isLightMode)) { - ResourcesCompat.getColor(resources, R.color.black, context.theme) - } else { + return if (!isOutgoingMessage && !isLightMode) { ResourcesCompat.getColor(resources, R.color.white, context.theme) + } else { + ResourcesCompat.getColor(resources, R.color.black, context.theme) } } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt index d71a34027..3172c0c63 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/VisibleMessageContentView.kt @@ -296,7 +296,7 @@ class VisibleMessageContentView : LinearLayout { fun getTextColor(context: Context, message: MessageRecord): Int { val isDayUiMode = UiModeUtilities.isDayUiMode(context) val colorID = if (message.isOutgoing) { - if (isDayUiMode) R.color.white else R.color.black + R.color.black } else { if (isDayUiMode) R.color.black else R.color.white } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java index 70db93a79..376630acb 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/RecipientDatabase.java @@ -238,10 +238,15 @@ public class RecipientDatabase extends Database { public void setApproved(@NonNull Recipient recipient, boolean approved) { ContentValues values = new ContentValues(); values.put(APPROVED, approved ? 1 : 0); - values.put(APPROVED_ME, approved ? 1 : 0); updateOrInsert(recipient.getAddress(), values); recipient.resolve().setApproved(approved); - recipient.resolve().setHasApprovedMe(approved); + } + + public void setApprovedMe(@NonNull Recipient recipient, boolean approvedMe) { + ContentValues values = new ContentValues(); + values.put(APPROVED_ME, approvedMe ? 1 : 0); + updateOrInsert(recipient.getAddress(), values); + recipient.resolve().setHasApprovedMe(approvedMe); } public void setBlocked(@NonNull Recipient recipient, boolean blocked) { 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 d6b5e824a..657e699b5 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/Storage.kt @@ -4,11 +4,22 @@ import android.content.Context import android.net.Uri import org.session.libsession.database.StorageProtocol import org.session.libsession.messaging.contacts.Contact -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.JobQueue +import org.session.libsession.messaging.jobs.MessageReceiveJob +import org.session.libsession.messaging.jobs.MessageSendJob +import org.session.libsession.messaging.jobs.TrimThreadJob import org.session.libsession.messaging.messages.control.ConfigurationMessage import org.session.libsession.messaging.messages.control.MessageRequestResponse -import org.session.libsession.messaging.messages.signal.* +import org.session.libsession.messaging.messages.signal.IncomingEncryptedMessage +import org.session.libsession.messaging.messages.signal.IncomingGroupMessage +import org.session.libsession.messaging.messages.signal.IncomingMediaMessage import org.session.libsession.messaging.messages.signal.IncomingTextMessage +import org.session.libsession.messaging.messages.signal.OutgoingGroupMediaMessage +import org.session.libsession.messaging.messages.signal.OutgoingMediaMessage +import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.messages.visible.Attachment import org.session.libsession.messaging.messages.visible.VisibleMessage import org.session.libsession.messaging.open_groups.OpenGroupV2 @@ -19,14 +30,17 @@ import org.session.libsession.messaging.sending_receiving.link_preview.LinkPrevi import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.utilities.UpdateMessageData import org.session.libsession.snode.OnionRequestAPI -import org.session.libsession.utilities.* +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.GroupUtil +import org.session.libsession.utilities.ProfileKeyUtil +import org.session.libsession.utilities.TextSecurePreferences import org.session.libsession.utilities.recipients.Recipient import org.session.libsignal.crypto.ecc.ECKeyPair import org.session.libsignal.messages.SignalServiceAttachmentPointer import org.session.libsignal.messages.SignalServiceGroup import org.session.libsignal.utilities.KeyHelper -import org.session.libsignal.utilities.Log import org.session.libsignal.utilities.guava.Optional import org.thoughtcrime.securesms.ApplicationContext import org.thoughtcrime.securesms.database.helpers.SQLCipherOpenHelper @@ -110,6 +124,14 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, senderAddress } val targetRecipient = Recipient.from(context, targetAddress, false) + if (!targetRecipient.isGroupRecipient) { + val recipientDb = DatabaseComponent.get(context).recipientDatabase() + if (isUserSender) { + recipientDb.setApproved(targetRecipient, true) + } else { + recipientDb.setApprovedMe(targetRecipient, true) + } + } 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!! }) @@ -585,7 +607,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, // create Thread if needed val threadId = threadDatabase.getOrCreateThreadIdFor(recipient) if (contact.didApproveMe == true) { - recipientDatabase.setApproved(recipient, true) + recipientDatabase.setApprovedMe(recipient, true) threadDatabase.setHasSent(threadId, true) } if (contact.isApproved == true) { @@ -663,7 +685,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, val mmsDb = DatabaseComponent.get(context).mmsDatabase() val senderAddress = fromSerialized(senderPublicKey) val requestSender = Recipient.from(context, senderAddress, false) - recipientDb.setApproved(requestSender, true) + recipientDb.setApprovedMe(requestSender, true) val message = IncomingMediaMessage( senderAddress, @@ -686,4 +708,12 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context, } } + override fun setRecipientApproved(recipient: Recipient, approved: Boolean) { + DatabaseComponent.get(context).recipientDatabase().setApproved(recipient, approved) + } + + override fun setRecipientApprovedMe(recipient: Recipient, approvedMe: Boolean) { + DatabaseComponent.get(context).recipientDatabase().setApprovedMe(recipient, approvedMe) + } + } \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt index ad118e0ea..296b4c787 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/repository/ConversationRepository.kt @@ -39,6 +39,7 @@ interface ConversationRepository { fun inviteContacts(threadId: Long, contacts: List) fun unblock(recipient: Recipient) fun deleteLocally(recipient: Recipient, message: MessageRecord) + fun setApproved(recipient: Recipient, isApproved: Boolean) suspend fun deleteForEveryone( threadId: Long, @@ -138,6 +139,10 @@ class DefaultConversationRepository @Inject constructor( messageDataProvider.deleteMessage(message.id, !message.isMms) } + override fun setApproved(recipient: Recipient, isApproved: Boolean) { + recipientDb.setApproved(recipient, isApproved) + } + override suspend fun deleteForEveryone( threadId: Long, recipient: Recipient, diff --git a/app/src/main/res/layout/contact_selection_list_divider.xml b/app/src/main/res/layout/contact_selection_list_divider.xml index 6e88fade9..ba3c825cb 100644 --- a/app/src/main/res/layout/contact_selection_list_divider.xml +++ b/app/src/main/res/layout/contact_selection_list_divider.xml @@ -11,7 +11,7 @@ android:paddingStart="16dp" android:paddingTop="10dp" android:paddingBottom="10dp" - android:textColor="@color/signal_primary_dark" + android:textColor="@color/text" tools:text="Recent chats"/> diff --git a/app/src/main/res/layout/view_visible_message.xml b/app/src/main/res/layout/view_visible_message.xml index 2e734034e..374883e56 100644 --- a/app/src/main/res/layout/view_visible_message.xml +++ b/app/src/main/res/layout/view_visible_message.xml @@ -75,6 +75,7 @@ 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 ee09713a1..f3436b8b4 100644 --- a/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt +++ b/libsession/src/main/java/org/session/libsession/database/StorageProtocol.kt @@ -4,7 +4,6 @@ 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 @@ -158,4 +157,6 @@ interface StorageProtocol { fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List, groupPublicKey: String?, openGroupID: String?, attachments: List): Long? fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) fun insertMessageRequestResponse(response: MessageRequestResponse) + fun setRecipientApproved(recipient: Recipient, approved: Boolean) + fun setRecipientApprovedMe(recipient: Recipient, approvedMe: Boolean) }