From 70aab2994b51896472e08de0d892f64f00b946a4 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 May 2023 16:46:48 +0930 Subject: [PATCH 1/7] Refactor MmsDatabase (#1169) --- .../securesms/database/MmsDatabase.kt | 155 +++++------------- .../thoughtcrime/securesms/util/CursorUtil.kt | 6 + 2 files changed, 43 insertions(+), 118 deletions(-) create mode 100644 app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.kt diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt index 90d3cad454..3a4b35ad17 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt @@ -65,6 +65,7 @@ import org.thoughtcrime.securesms.database.model.Quote import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get import org.thoughtcrime.securesms.mms.MmsException import org.thoughtcrime.securesms.mms.SlideDeck +import org.thoughtcrime.securesms.util.asSequence import java.io.Closeable import java.io.IOException import java.security.SecureRandom @@ -91,54 +92,22 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa return 0 } - fun addFailures(messageId: Long, failure: List) { - try { - addToDocument(messageId, NETWORK_FAILURE, failure, NetworkFailureList::class.java) - } catch (e: IOException) { - Log.w(TAG, e) + fun isOutgoingMessage(timestamp: Long): Boolean = + databaseHelper.writableDatabase.query( + TABLE_NAME, + arrayOf(ID, THREAD_ID, MESSAGE_BOX, ADDRESS), + DATE_SENT + " = ?", + arrayOf(timestamp.toString()), + null, + null, + null, + null + ).use { cursor -> + cursor.asSequence() + .map { cursor.getColumnIndexOrThrow(MESSAGE_BOX) } + .map(cursor::getLong) + .any { MmsSmsColumns.Types.isOutgoingMessageType(it) } } - } - - fun removeFailure(messageId: Long, failure: NetworkFailure?) { - try { - removeFromDocument(messageId, NETWORK_FAILURE, failure, NetworkFailureList::class.java) - } catch (e: IOException) { - Log.w(TAG, e) - } - } - - fun isOutgoingMessage(timestamp: Long): Boolean { - val database = databaseHelper.writableDatabase - var cursor: Cursor? = null - var isOutgoing = false - try { - cursor = database.query( - TABLE_NAME, - arrayOf(ID, THREAD_ID, MESSAGE_BOX, ADDRESS), - DATE_SENT + " = ?", - arrayOf(timestamp.toString()), - null, - null, - null, - null - ) - while (cursor.moveToNext()) { - if (MmsSmsColumns.Types.isOutgoingMessageType( - cursor.getLong( - cursor.getColumnIndexOrThrow( - MESSAGE_BOX - ) - ) - ) - ) { - isOutgoing = true - } - } - } finally { - cursor?.close() - } - return isOutgoing - } fun incrementReceiptCount( messageId: SyncMessageId, @@ -254,15 +223,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa } } - private fun getThreadIdFor(notification: NotificationInd): Long { - val fromString = - if (notification.from != null && notification.from.textString != null) toIsoString( - notification.from.textString - ) else "" - val recipient = Recipient.from(context, fromExternal(context, fromString), false) - return get(context).threadDatabase().getOrCreateThreadIdFor(recipient) - } - private fun rawQuery(where: String, arguments: Array?): Cursor { val database = databaseHelper.readableDatabase return database.rawQuery( @@ -273,10 +233,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa ) } - fun getMessages(idsAsString: String): Cursor { - return rawQuery(idsAsString, null) - } - fun getMessage(messageId: Long): Cursor { val cursor = rawQuery(RAW_ID_WHERE, arrayOf(messageId.toString())) setNotifyConverationListeners(cursor, getThreadIdForMessage(messageId)) @@ -306,48 +262,30 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa } } - fun markAsPendingInsecureSmsFallback(messageId: Long) { - val threadId = getThreadIdForMessage(messageId) + private fun markAs( + messageId: Long, + baseType: Long, + threadId: Long = getThreadIdForMessage(messageId) + ) { updateMailboxBitmask( messageId, MmsSmsColumns.Types.BASE_TYPE_MASK, - MmsSmsColumns.Types.BASE_PENDING_INSECURE_SMS_FALLBACK, + baseType, Optional.of(threadId) ) notifyConversationListeners(threadId) } fun markAsSending(messageId: Long) { - val threadId = getThreadIdForMessage(messageId) - updateMailboxBitmask( - messageId, - MmsSmsColumns.Types.BASE_TYPE_MASK, - MmsSmsColumns.Types.BASE_SENDING_TYPE, - Optional.of(threadId) - ) - notifyConversationListeners(threadId) + markAs(messageId, MmsSmsColumns.Types.BASE_SENDING_TYPE) } fun markAsSentFailed(messageId: Long) { - val threadId = getThreadIdForMessage(messageId) - updateMailboxBitmask( - messageId, - MmsSmsColumns.Types.BASE_TYPE_MASK, - MmsSmsColumns.Types.BASE_SENT_FAILED_TYPE, - Optional.of(threadId) - ) - notifyConversationListeners(threadId) + markAs(messageId, MmsSmsColumns.Types.BASE_SENT_FAILED_TYPE) } override fun markAsSent(messageId: Long, secure: Boolean) { - val threadId = getThreadIdForMessage(messageId) - updateMailboxBitmask( - messageId, - MmsSmsColumns.Types.BASE_TYPE_MASK, - MmsSmsColumns.Types.BASE_SENT_TYPE or if (secure) MmsSmsColumns.Types.PUSH_MESSAGE_BIT or MmsSmsColumns.Types.SECURE_MESSAGE_BIT else 0, - Optional.of(threadId) - ) - notifyConversationListeners(threadId) + markAs(messageId, MmsSmsColumns.Types.BASE_SENT_TYPE or if (secure) MmsSmsColumns.Types.PUSH_MESSAGE_BIT or MmsSmsColumns.Types.SECURE_MESSAGE_BIT else 0) } override fun markUnidentified(messageId: Long, unidentified: Boolean) { @@ -371,13 +309,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa val mentionChange = if (hasMention) { 1 } else { 0 } get(context).threadDatabase().decrementUnread(threadId, 1, mentionChange) } - updateMailboxBitmask( - messageId, - MmsSmsColumns.Types.BASE_TYPE_MASK, - MmsSmsColumns.Types.BASE_DELETED_TYPE, - Optional.of(threadId) - ) - notifyConversationListeners(threadId) + markAs(messageId, MmsSmsColumns.Types.BASE_DELETED_TYPE, threadId) } override fun markExpireStarted(messageId: Long) { @@ -407,10 +339,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa ) } - fun setAllMessagesRead(): List { - return setMessagesRead(READ + " = 0", null) - } - private fun setMessagesRead(where: String, arguments: Array?): List { val database = databaseHelper.writableDatabase val result: MutableList = LinkedList() @@ -419,7 +347,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa try { cursor = database.query( TABLE_NAME, - arrayOf(ID, ADDRESS, DATE_SENT, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED), + arrayOf(ID, ADDRESS, DATE_SENT, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED), where, arguments, null, @@ -1400,25 +1328,16 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa val attachments = get(context).attachmentDatabase().getAttachment( cursor ) - val contacts: List = getSharedContacts( - cursor, attachments - ) - val contactAttachments = - contacts.map { obj: Contact? -> obj!!.avatarAttachment } - .filter { a: Attachment? -> a != null } - .toSet() - val previews: List = getLinkPreviews( - cursor, attachments - ) - val previewAttachments = - previews.filter { lp: LinkPreview? -> lp!!.getThumbnail().isPresent } - .map { lp: LinkPreview? -> lp!!.getThumbnail().get() } - .toSet() + val contacts: List = getSharedContacts(cursor, attachments) + val contactAttachments: Set = + contacts.mapNotNull { it?.avatarAttachment }.toSet() + val previews: List = getLinkPreviews(cursor, attachments) + val previewAttachments: Set = + previews.mapNotNull { it?.getThumbnail()?.orNull() }.toSet() val slideDeck = getSlideDeck( - Stream.of(attachments) - .filterNot { o: DatabaseAttachment? -> contactAttachments.contains(o) } - .filterNot { o: DatabaseAttachment? -> previewAttachments.contains(o) } - .toList() + attachments + .filterNot { o: DatabaseAttachment? -> o in contactAttachments } + .filterNot { o: DatabaseAttachment? -> o in previewAttachments } ) val quote = getQuote(cursor) val reactions = get(context).reactionDatabase().getReactions(cursor) @@ -1623,4 +1542,4 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa const val CREATE_REACTIONS_LAST_SEEN_COMMAND = "ALTER TABLE $TABLE_NAME ADD COLUMN $REACTIONS_LAST_SEEN INTEGER DEFAULT 0;" const val CREATE_HAS_MENTION_COMMAND = "ALTER TABLE $TABLE_NAME ADD COLUMN $HAS_MENTION INTEGER DEFAULT 0;" } -} \ No newline at end of file +} diff --git a/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.kt new file mode 100644 index 0000000000..9cff8c77bc --- /dev/null +++ b/app/src/main/java/org/thoughtcrime/securesms/util/CursorUtil.kt @@ -0,0 +1,6 @@ +package org.thoughtcrime.securesms.util + +import android.database.Cursor + +fun Cursor.asSequence(): Sequence = + generateSequence { if (moveToNext()) this else null } From a934c5c2e2e73ad6fb245b3dc88adbcdd15264db Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 May 2023 16:49:01 +0930 Subject: [PATCH 2/7] Fix missing media progress (#1151) --- .../securesms/conversation/v2/utilities/ThumbnailView.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt index e9a085dad9..4a9986d6ec 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/utilities/ThumbnailView.kt @@ -126,10 +126,9 @@ open class ThumbnailView: FrameLayout { buildThumbnailGlideRequest(glide, slide).into(GlideDrawableListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, result)) } slide.hasPlaceholder() -> { - buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, result)) + buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(binding.thumbnailImage, null, result)) } else -> { - binding.thumbnailLoadIndicator.isVisible = false glide.clear(binding.thumbnailImage) result.set(false) } From 83b6002a270bca19b98826be0ec0b9d98885c890 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 May 2023 16:49:13 +0930 Subject: [PATCH 3/7] Fix emoji misalignment (#1155) --- .../v2/ConversationReactionOverlay.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java index b3c151690f..1d81325e03 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationReactionOverlay.java @@ -203,10 +203,11 @@ public final class ConversationReactionOverlay extends FrameLayout { boolean isMessageOnLeft) { contextMenu = new ConversationContextMenu(dropdownAnchor, getMenuActionItems(messageRecord)); - float itemX = isMessageOnLeft ? scrubberHorizontalMargin : + float endX = isMessageOnLeft ? scrubberHorizontalMargin : selectedConversationModel.getBubbleX() - conversationItem.getWidth() + selectedConversationModel.getBubbleWidth(); - conversationItem.setX(itemX); - conversationItem.setY(selectedConversationModel.getBubbleY() - statusBarHeight); + float endY = selectedConversationModel.getBubbleY() - statusBarHeight; + conversationItem.setX(endX); + conversationItem.setY(endY); Bitmap conversationItemSnapshot = selectedConversationModel.getBitmap(); boolean isWideLayout = contextMenu.getMaxWidth() + scrubberWidth < getWidth(); @@ -214,8 +215,6 @@ public final class ConversationReactionOverlay extends FrameLayout { int overlayHeight = getHeight(); int bubbleWidth = selectedConversationModel.getBubbleWidth(); - float endX = itemX; - float endY = conversationItem.getY(); float endApparentTop = endY; float endScale = 1f; @@ -265,9 +264,7 @@ public final class ConversationReactionOverlay extends FrameLayout { } } else { endY = overlayHeight - contextMenu.getMaxHeight() - menuPadding - conversationItemSnapshot.getHeight(); - - float contextMenuTop = endY + conversationItemSnapshot.getHeight(); - reactionBarBackgroundY = getReactionBarOffsetForTouch(selectedConversationModel.getBubbleY(), contextMenuTop, menuPadding, reactionBarOffset, reactionBarHeight, reactionBarTopPadding, endY); + reactionBarBackgroundY = endY - reactionBarHeight - menuPadding; } endApparentTop = endY; From 99cb10f5befe288a61e56804207808b9972db200 Mon Sep 17 00:00:00 2001 From: Andrew Date: Mon, 1 May 2023 16:49:19 +0930 Subject: [PATCH 4/7] Add send approval message (#1157) --- .../conversation/v2/ConversationActivityV2.kt | 8 ++++++++ .../main/res/layout/activity_conversation_v2.xml | 14 +++++++++++++- app/src/main/res/values/strings.xml | 1 + 3 files changed, 22 insertions(+), 1 deletion(-) 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 b5f1b96fd7..cabb583085 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 @@ -22,6 +22,7 @@ import androidx.activity.viewModels import androidx.annotation.DimenRes import androidx.appcompat.app.AlertDialog import androidx.core.view.drawToBitmap +import androidx.core.view.isGone import androidx.core.view.isVisible import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider @@ -344,6 +345,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe updateSubtitle() setUpBlockedBanner() binding!!.searchBottomBar.setEventListener(this) + updateSendAfterApprovalText() showOrHideInputIfNeeded() setUpMessageRequestsBar() @@ -653,7 +655,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe setUpMessageRequestsBar() invalidateOptionsMenu() updateSubtitle() + updateSendAfterApprovalText() showOrHideInputIfNeeded() + binding?.toolbarContent?.profilePictureView?.root?.update(threadRecipient) binding?.toolbarContent?.conversationTitleView?.text = when { threadRecipient.isLocalNumber -> getString(R.string.note_to_self) @@ -662,6 +666,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } } + private fun updateSendAfterApprovalText() { + binding?.textSendAfterApproval?.isGone = viewModel.recipient?.hasApprovedMe() ?: true + } + private fun showOrHideInputIfNeeded() { val recipient = viewModel.recipient if (recipient != null && recipient.isClosedGroupRecipient) { diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index 4ec4666d7f..d2696a45ec 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -35,7 +35,7 @@ android:layout_width="match_parent" android:layout_height="36dp" android:visibility="gone" - android:layout_above="@+id/messageRequestBar" + android:layout_above="@+id/textSendAfterApproval" /> + + %1$d of %2$d Call Permissions Required You can enable the \'Voice and video calls\' permission in the Privacy Settings. + You will be able to send voice messages and attachments once the recipient has approved this message request Delete selected message? From ba3566f7e883d90287749ae88583a483b7eb5f5a Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 11 May 2023 09:30:39 +0930 Subject: [PATCH 5/7] Fix approval text position when input bar is gone (#1181) --- app/src/main/res/layout/activity_conversation_v2.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/layout/activity_conversation_v2.xml b/app/src/main/res/layout/activity_conversation_v2.xml index d2696a45ec..8992a660d7 100644 --- a/app/src/main/res/layout/activity_conversation_v2.xml +++ b/app/src/main/res/layout/activity_conversation_v2.xml @@ -128,6 +128,7 @@ android:textSize="12sp" android:layout_width="match_parent" android:layout_height="wrap_content" + android:layout_alignWithParentIfMissing="true" android:layout_above="@id/messageRequestBar"/> Date: Thu, 11 May 2023 09:31:05 +0930 Subject: [PATCH 6/7] Fix message request button overlap (#1188) --- .../main/res/layout/view_message_request_banner.xml | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/layout/view_message_request_banner.xml b/app/src/main/res/layout/view_message_request_banner.xml index d37fde8a3f..c03e9c1cdf 100644 --- a/app/src/main/res/layout/view_message_request_banner.xml +++ b/app/src/main/res/layout/view_message_request_banner.xml @@ -3,9 +3,9 @@ xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" - android:contentDescription="@string/AccessibilityId_message_request_banner" android:layout_height="wrap_content" android:background="@drawable/conversation_view_background" + android:contentDescription="@string/AccessibilityId_message_request_banner" android:gravity="center_vertical" android:orientation="horizontal" android:paddingStart="@dimen/accent_line_thickness" @@ -72,11 +72,15 @@ android:alpha="0.4" android:ellipsize="end" android:maxLines="1" + android:textAlignment="textEnd" android:textColor="?android:textColorPrimary" android:textSize="@dimen/small_font_size" + app:layout_constrainedWidth="true" app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintHorizontal_bias="1" + app:layout_constraintStart_toEndOf="@id/unreadCountIndicator" app:layout_constraintTop_toTopOf="parent" - tools:text="9:41 AM" /> + tools:text="11 Apr, 9:41 AM" /> - \ No newline at end of file + From 8dbabec4e7c90a975df1307bba2c810bbff67b42 Mon Sep 17 00:00:00 2001 From: Andrew Date: Thu, 11 May 2023 09:32:38 +0930 Subject: [PATCH 7/7] Fix send after approval message (#1178) * Fix send after approval message * Fix logic * Utilise isLocalNumber --- .../securesms/conversation/v2/ConversationActivityV2.kt | 2 +- .../securesms/conversation/v2/ConversationViewModel.kt | 3 +++ 2 files changed, 4 insertions(+), 1 deletion(-) 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 cabb583085..c30a491a8f 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 @@ -667,7 +667,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe } private fun updateSendAfterApprovalText() { - binding?.textSendAfterApproval?.isGone = viewModel.recipient?.hasApprovedMe() ?: true + binding?.textSendAfterApproval?.isVisible = viewModel.showSendAfterApprovalText } private fun showOrHideInputIfNeeded() { 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 7f6768ee93..fc557f7260 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 @@ -31,6 +31,9 @@ class ConversationViewModel( private val storage: Storage ) : ViewModel() { + val showSendAfterApprovalText: Boolean + get() = recipient?.run { isContactRecipient && !isLocalNumber && !hasApprovedMe() } ?: false + private val _uiState = MutableStateFlow(ConversationUiState()) val uiState: StateFlow = _uiState