Merge remote-tracking branch 'upstream/dev' into libsession-integration

# Conflicts:
#	app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationViewModel.kt
#	app/src/main/java/org/thoughtcrime/securesms/database/MmsDatabase.kt
This commit is contained in:
0x330a 2023-05-11 10:29:13 +10:00
commit 14eb04adcb
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C
9 changed files with 101 additions and 119 deletions

View File

@ -40,6 +40,7 @@ import androidx.appcompat.app.AlertDialog
import androidx.core.text.set import androidx.core.text.set
import androidx.core.text.toSpannable import androidx.core.text.toSpannable
import androidx.core.view.drawToBitmap import androidx.core.view.drawToBitmap
import androidx.core.view.isGone
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.Lifecycle import androidx.lifecycle.Lifecycle
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
@ -409,6 +410,7 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
updatePlaceholder() updatePlaceholder()
setUpBlockedBanner() setUpBlockedBanner()
binding!!.searchBottomBar.setEventListener(this) binding!!.searchBottomBar.setEventListener(this)
updateSendAfterApprovalText()
showOrHideInputIfNeeded() showOrHideInputIfNeeded()
setUpMessageRequestsBar() setUpMessageRequestsBar()
@ -741,7 +743,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
setUpMessageRequestsBar() setUpMessageRequestsBar()
invalidateOptionsMenu() invalidateOptionsMenu()
updateSubtitle() updateSubtitle()
updateSendAfterApprovalText()
showOrHideInputIfNeeded() showOrHideInputIfNeeded()
binding?.toolbarContent?.profilePictureView?.root?.update(threadRecipient) binding?.toolbarContent?.profilePictureView?.root?.update(threadRecipient)
binding?.toolbarContent?.conversationTitleView?.text = when { binding?.toolbarContent?.conversationTitleView?.text = when {
threadRecipient.isLocalNumber -> getString(R.string.note_to_self) threadRecipient.isLocalNumber -> getString(R.string.note_to_self)
@ -750,6 +754,10 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
} }
} }
private fun updateSendAfterApprovalText() {
binding?.textSendAfterApproval?.isVisible = viewModel.showSendAfterApprovalText
}
private fun showOrHideInputIfNeeded() { private fun showOrHideInputIfNeeded() {
val recipient = viewModel.recipient val recipient = viewModel.recipient
if (recipient != null && recipient.isClosedGroupRecipient) { if (recipient != null && recipient.isClosedGroupRecipient) {

View File

@ -203,10 +203,11 @@ public final class ConversationReactionOverlay extends FrameLayout {
boolean isMessageOnLeft) { boolean isMessageOnLeft) {
contextMenu = new ConversationContextMenu(dropdownAnchor, getMenuActionItems(messageRecord)); contextMenu = new ConversationContextMenu(dropdownAnchor, getMenuActionItems(messageRecord));
float itemX = isMessageOnLeft ? scrubberHorizontalMargin : float endX = isMessageOnLeft ? scrubberHorizontalMargin :
selectedConversationModel.getBubbleX() - conversationItem.getWidth() + selectedConversationModel.getBubbleWidth(); selectedConversationModel.getBubbleX() - conversationItem.getWidth() + selectedConversationModel.getBubbleWidth();
conversationItem.setX(itemX); float endY = selectedConversationModel.getBubbleY() - statusBarHeight;
conversationItem.setY(selectedConversationModel.getBubbleY() - statusBarHeight); conversationItem.setX(endX);
conversationItem.setY(endY);
Bitmap conversationItemSnapshot = selectedConversationModel.getBitmap(); Bitmap conversationItemSnapshot = selectedConversationModel.getBitmap();
boolean isWideLayout = contextMenu.getMaxWidth() + scrubberWidth < getWidth(); boolean isWideLayout = contextMenu.getMaxWidth() + scrubberWidth < getWidth();
@ -214,8 +215,6 @@ public final class ConversationReactionOverlay extends FrameLayout {
int overlayHeight = getHeight(); int overlayHeight = getHeight();
int bubbleWidth = selectedConversationModel.getBubbleWidth(); int bubbleWidth = selectedConversationModel.getBubbleWidth();
float endX = itemX;
float endY = conversationItem.getY();
float endApparentTop = endY; float endApparentTop = endY;
float endScale = 1f; float endScale = 1f;
@ -265,9 +264,7 @@ public final class ConversationReactionOverlay extends FrameLayout {
} }
} else { } else {
endY = overlayHeight - contextMenu.getMaxHeight() - menuPadding - conversationItemSnapshot.getHeight(); endY = overlayHeight - contextMenu.getMaxHeight() - menuPadding - conversationItemSnapshot.getHeight();
reactionBarBackgroundY = endY - reactionBarHeight - menuPadding;
float contextMenuTop = endY + conversationItemSnapshot.getHeight();
reactionBarBackgroundY = getReactionBarOffsetForTouch(selectedConversationModel.getBubbleY(), contextMenuTop, menuPadding, reactionBarOffset, reactionBarHeight, reactionBarTopPadding, endY);
} }
endApparentTop = endY; endApparentTop = endY;

View File

@ -35,6 +35,9 @@ class ConversationViewModel(
private val storage: Storage private val storage: Storage
) : ViewModel() { ) : ViewModel() {
val showSendAfterApprovalText: Boolean
get() = recipient?.run { isContactRecipient && !isLocalNumber && !hasApprovedMe() } ?: false
private val _uiState = MutableStateFlow(ConversationUiState(conversationExists = true)) private val _uiState = MutableStateFlow(ConversationUiState(conversationExists = true))
val uiState: StateFlow<ConversationUiState> = _uiState val uiState: StateFlow<ConversationUiState> = _uiState

View File

@ -126,10 +126,9 @@ open class ThumbnailView: FrameLayout {
buildThumbnailGlideRequest(glide, slide).into(GlideDrawableListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, result)) buildThumbnailGlideRequest(glide, slide).into(GlideDrawableListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, result))
} }
slide.hasPlaceholder() -> { slide.hasPlaceholder() -> {
buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(binding.thumbnailImage, binding.thumbnailLoadIndicator, result)) buildPlaceholderGlideRequest(glide, slide).into(GlideBitmapListeningTarget(binding.thumbnailImage, null, result))
} }
else -> { else -> {
binding.thumbnailLoadIndicator.isVisible = false
glide.clear(binding.thumbnailImage) glide.clear(binding.thumbnailImage)
result.set(false) result.set(false)
} }

View File

@ -60,6 +60,7 @@ import org.thoughtcrime.securesms.database.model.Quote
import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get import org.thoughtcrime.securesms.dependencies.DatabaseComponent.Companion.get
import org.thoughtcrime.securesms.mms.MmsException import org.thoughtcrime.securesms.mms.MmsException
import org.thoughtcrime.securesms.mms.SlideDeck import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.util.asSequence
import java.io.Closeable import java.io.Closeable
import java.io.IOException import java.io.IOException
import java.security.SecureRandom import java.security.SecureRandom
@ -86,53 +87,21 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
return 0 return 0
} }
fun addFailures(messageId: Long, failure: List<NetworkFailure>) { fun isOutgoingMessage(timestamp: Long): Boolean =
try { databaseHelper.writableDatabase.query(
addToDocument(messageId, NETWORK_FAILURE, failure, NetworkFailureList::class.java)
} catch (e: IOException) {
Log.w(TAG, e)
}
}
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, TABLE_NAME,
arrayOf<String>(ID, THREAD_ID, MESSAGE_BOX, ADDRESS), arrayOf(ID, THREAD_ID, MESSAGE_BOX, ADDRESS),
DATE_SENT + " = ?", DATE_SENT + " = ?",
arrayOf(timestamp.toString()), arrayOf(timestamp.toString()),
null, null,
null, null,
null, null,
null null
) ).use { cursor ->
while (cursor.moveToNext()) { cursor.asSequence()
if (MmsSmsColumns.Types.isOutgoingMessageType( .map { cursor.getColumnIndexOrThrow(MESSAGE_BOX) }
cursor.getLong( .map(cursor::getLong)
cursor.getColumnIndexOrThrow( .any { MmsSmsColumns.Types.isOutgoingMessageType(it) }
MESSAGE_BOX
)
)
)
) {
isOutgoing = true
}
}
} finally {
cursor?.close()
}
return isOutgoing
} }
fun incrementReceiptCount( fun incrementReceiptCount(
@ -230,6 +199,25 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
} }
} }
@Throws(RecipientFormattingException::class, MmsException::class)
private fun getThreadIdFor(retrieved: IncomingMediaMessage): Long {
return if (retrieved.groupId != null) {
val groupRecipients = Recipient.from(
context,
retrieved.groupId,
true
)
get(context).threadDatabase().getOrCreateThreadIdFor(groupRecipients)
} else {
val sender = Recipient.from(
context,
retrieved.from,
true
)
get(context).threadDatabase().getOrCreateThreadIdFor(sender)
}
}
private fun rawQuery(where: String, arguments: Array<String>?): Cursor { private fun rawQuery(where: String, arguments: Array<String>?): Cursor {
val database = databaseHelper.readableDatabase val database = databaseHelper.readableDatabase
return database.rawQuery( return database.rawQuery(
@ -269,48 +257,30 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
} }
} }
fun markAsPendingInsecureSmsFallback(messageId: Long) { private fun markAs(
val threadId = getThreadIdForMessage(messageId) messageId: Long,
baseType: Long,
threadId: Long = getThreadIdForMessage(messageId)
) {
updateMailboxBitmask( updateMailboxBitmask(
messageId, messageId,
MmsSmsColumns.Types.BASE_TYPE_MASK, MmsSmsColumns.Types.BASE_TYPE_MASK,
MmsSmsColumns.Types.BASE_PENDING_INSECURE_SMS_FALLBACK, baseType,
Optional.of(threadId) Optional.of(threadId)
) )
notifyConversationListeners(threadId) notifyConversationListeners(threadId)
} }
fun markAsSending(messageId: Long) { fun markAsSending(messageId: Long) {
val threadId = getThreadIdForMessage(messageId) markAs(messageId, MmsSmsColumns.Types.BASE_SENDING_TYPE)
updateMailboxBitmask(
messageId,
MmsSmsColumns.Types.BASE_TYPE_MASK,
MmsSmsColumns.Types.BASE_SENDING_TYPE,
Optional.of(threadId)
)
notifyConversationListeners(threadId)
} }
fun markAsSentFailed(messageId: Long) { fun markAsSentFailed(messageId: Long) {
val threadId = getThreadIdForMessage(messageId) markAs(messageId, MmsSmsColumns.Types.BASE_SENT_FAILED_TYPE)
updateMailboxBitmask(
messageId,
MmsSmsColumns.Types.BASE_TYPE_MASK,
MmsSmsColumns.Types.BASE_SENT_FAILED_TYPE,
Optional.of(threadId)
)
notifyConversationListeners(threadId)
} }
override fun markAsSent(messageId: Long, secure: Boolean) { override fun markAsSent(messageId: Long, secure: Boolean) {
val threadId = getThreadIdForMessage(messageId) markAs(messageId, MmsSmsColumns.Types.BASE_SENT_TYPE or if (secure) MmsSmsColumns.Types.PUSH_MESSAGE_BIT or MmsSmsColumns.Types.SECURE_MESSAGE_BIT else 0)
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)
} }
override fun markUnidentified(messageId: Long, unidentified: Boolean) { override fun markUnidentified(messageId: Long, unidentified: Boolean) {
@ -331,13 +301,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
queue(Runnable { attachmentDatabase.deleteAttachmentsForMessage(messageId) }) queue(Runnable { attachmentDatabase.deleteAttachmentsForMessage(messageId) })
val threadId = getThreadIdForMessage(messageId) val threadId = getThreadIdForMessage(messageId)
updateMailboxBitmask( markAs(messageId, MmsSmsColumns.Types.BASE_DELETED_TYPE, threadId)
messageId,
MmsSmsColumns.Types.BASE_TYPE_MASK,
MmsSmsColumns.Types.BASE_DELETED_TYPE,
Optional.of(threadId)
)
notifyConversationListeners(threadId)
} }
override fun markExpireStarted(messageId: Long) { override fun markExpireStarted(messageId: Long) {
@ -374,10 +338,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
) )
} }
fun setAllMessagesRead(): List<MarkedMessageInfo> {
return setMessagesRead(READ + " = 0", null)
}
private fun setMessagesRead(where: String, arguments: Array<String>?): List<MarkedMessageInfo> { private fun setMessagesRead(where: String, arguments: Array<String>?): List<MarkedMessageInfo> {
val database = databaseHelper.writableDatabase val database = databaseHelper.writableDatabase
val result: MutableList<MarkedMessageInfo> = LinkedList() val result: MutableList<MarkedMessageInfo> = LinkedList()
@ -386,7 +346,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
try { try {
cursor = database.query( cursor = database.query(
TABLE_NAME, TABLE_NAME,
arrayOf<String>(ID, ADDRESS, DATE_SENT, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED), arrayOf(ID, ADDRESS, DATE_SENT, MESSAGE_BOX, EXPIRES_IN, EXPIRE_STARTED),
where, where,
arguments, arguments,
null, null,
@ -1333,25 +1293,16 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
val attachments = get(context).attachmentDatabase().getAttachment( val attachments = get(context).attachmentDatabase().getAttachment(
cursor cursor
) )
val contacts: List<Contact?> = getSharedContacts( val contacts: List<Contact?> = getSharedContacts(cursor, attachments)
cursor, attachments val contactAttachments: Set<Attachment?> =
) contacts.mapNotNull { it?.avatarAttachment }.toSet()
val contactAttachments = val previews: List<LinkPreview?> = getLinkPreviews(cursor, attachments)
contacts.map { obj: Contact? -> obj!!.avatarAttachment } val previewAttachments: Set<Attachment?> =
.filter { a: Attachment? -> a != null } previews.mapNotNull { it?.getThumbnail()?.orNull() }.toSet()
.toSet()
val previews: List<LinkPreview?> = getLinkPreviews(
cursor, attachments
)
val previewAttachments =
previews.filter { lp: LinkPreview? -> lp!!.getThumbnail().isPresent }
.map { lp: LinkPreview? -> lp!!.getThumbnail().get() }
.toSet()
val slideDeck = getSlideDeck( val slideDeck = getSlideDeck(
Stream.of(attachments) attachments
.filterNot { o: DatabaseAttachment? -> contactAttachments.contains(o) } .filterNot { o: DatabaseAttachment? -> o in contactAttachments }
.filterNot { o: DatabaseAttachment? -> previewAttachments.contains(o) } .filterNot { o: DatabaseAttachment? -> o in previewAttachments }
.toList()
) )
val quote = getQuote(cursor) val quote = getQuote(cursor)
val reactions = get(context).reactionDatabase().getReactions(cursor) val reactions = get(context).reactionDatabase().getReactions(cursor)

View File

@ -0,0 +1,6 @@
package org.thoughtcrime.securesms.util
import android.database.Cursor
fun Cursor.asSequence(): Sequence<Cursor> =
generateSequence { if (moveToNext()) this else null }

View File

@ -35,7 +35,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="36dp" android:layout_height="36dp"
android:visibility="gone" android:visibility="gone"
android:layout_above="@+id/messageRequestBar" android:layout_above="@+id/textSendAfterApproval"
/> />
<org.thoughtcrime.securesms.conversation.v2.input_bar.InputBar <org.thoughtcrime.securesms.conversation.v2.input_bar.InputBar
@ -118,6 +118,19 @@
</LinearLayout> </LinearLayout>
<TextView
android:id="@+id/textSendAfterApproval"
android:text="@string/ConversationActivity_send_after_approval"
android:visibility="gone"
android:textAlignment="center"
android:textColor="@color/classic_light_2"
android:padding="22dp"
android:textSize="12sp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_alignWithParentIfMissing="true"
android:layout_above="@id/messageRequestBar"/>
<RelativeLayout <RelativeLayout
android:id="@+id/scrollToBottomButton" android:id="@+id/scrollToBottomButton"
android:visibility="gone" android:visibility="gone"

View File

@ -3,9 +3,9 @@
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:contentDescription="@string/AccessibilityId_message_request_banner"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:background="@drawable/conversation_view_background" android:background="@drawable/conversation_view_background"
android:contentDescription="@string/AccessibilityId_message_request_banner"
android:gravity="center_vertical" android:gravity="center_vertical"
android:orientation="horizontal" android:orientation="horizontal"
android:paddingStart="@dimen/accent_line_thickness" android:paddingStart="@dimen/accent_line_thickness"
@ -72,11 +72,15 @@
android:alpha="0.4" android:alpha="0.4"
android:ellipsize="end" android:ellipsize="end"
android:maxLines="1" android:maxLines="1"
android:textAlignment="textEnd"
android:textColor="?android:textColorPrimary" android:textColor="?android:textColorPrimary"
android:textSize="@dimen/small_font_size" android:textSize="@dimen/small_font_size"
app:layout_constrainedWidth="true"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHorizontal_bias="1"
app:layout_constraintStart_toEndOf="@id/unreadCountIndicator"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
tools:text="9:41 AM" /> tools:text="11 Apr, 9:41 AM" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -227,6 +227,7 @@
<string name="ConversationActivity_search_position">%1$d of %2$d</string> <string name="ConversationActivity_search_position">%1$d of %2$d</string>
<string name="ConversationActivity_call_title">Call Permissions Required</string> <string name="ConversationActivity_call_title">Call Permissions Required</string>
<string name="ConversationActivity_call_prompt">You can enable the \'Voice and video calls\' permission in the Privacy Settings.</string> <string name="ConversationActivity_call_prompt">You can enable the \'Voice and video calls\' permission in the Privacy Settings.</string>
<string name="ConversationActivity_send_after_approval">You will be able to send voice messages and attachments once the recipient has approved this message request</string>
<!-- ConversationFragment --> <!-- ConversationFragment -->
<plurals name="ConversationFragment_delete_selected_messages"> <plurals name="ConversationFragment_delete_selected_messages">
<item quantity="one">Delete selected message?</item> <item quantity="one">Delete selected message?</item>