Add various fixes for pinned messages.

This commit is contained in:
Michelle Tang
2025-11-26 11:31:21 -05:00
committed by jeffrey-signal
parent 864867f60e
commit 804f479cb0
4 changed files with 121 additions and 3 deletions

View File

@@ -685,11 +685,10 @@ public final class ConversationUpdateItem extends FrameLayout
passthroughClickListener.onClick(v);
}
});
} else if (MessageRecordUtil.hasPinnedMessageUpdate(conversationMessage.getMessageRecord())) {
} else if (MessageRecordUtil.hasPinnedMessageUpdate(conversationMessage.getMessageRecord()) && conversationMessage.getMessageRecord().getMessageExtras().pinnedMessage.pinnedMessageId != -1) {
actionButton.setText(R.string.PinnedMessage__go_to_message);
actionButton.setVisibility(VISIBLE);
actionButton.setOnClickListener(v -> {
// TODO(michelle): Handle when a message gets deleted
if (batchSelected.isEmpty() && eventListener != null && MessageRecordUtil.hasPinnedMessageUpdate(conversationMessage.getMessageRecord())) {
eventListener.onViewPinnedMessage(conversationMessage.getMessageRecord().getMessageExtras().pinnedMessage.pinnedMessageId);
} else {

View File

@@ -682,6 +682,8 @@ public final class ConversationListItem extends ConstraintLayout implements Bind
}
} else if (MessageTypes.isPollTerminate(thread.getType())) {
return emphasisAdded(context, thread.getBody(), Glyph.POLL, defaultTint);
} else if (MessageTypes.isPinnedMessageUpdate(thread.getType())) {
return emphasisAdded(context, thread.getBody(), Glyph.PIN, defaultTint);
} else {
ThreadTable.Extra extra = thread.getExtra();
if (extra != null && extra.isViewOnce()) {

View File

@@ -2270,6 +2270,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
disassociateStoryQuotes(messageId)
polls.deletePoll(messageId)
disassociatePollFromPollTerminate(polls.getPollTerminateMessageId(messageId))
disassociatePinnedMessage(messageId)
val threadId = getThreadIdForMessage(messageId)
threads.update(threadId, false)
@@ -2820,7 +2821,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
retrieved.type == MessageType.IDENTITY_VERIFIED ||
retrieved.type == MessageType.IDENTITY_UPDATE
val read = silent || retrieved.type == MessageType.EXPIRATION_UPDATE
val read = silent || retrieved.type == MessageType.EXPIRATION_UPDATE || MessageTypes.isPinnedMessageUpdate(type)
val contentValues = contentValuesOf(
DATE_SENT to retrieved.sentTimeMillis,
@@ -3669,6 +3670,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
groupReceipts.deleteRowsForMessage(messageId)
mentions.deleteMentionsForMessage(messageId)
disassociatePollFromPollTerminate(polls.getPollTerminateMessageId(messageId))
disassociatePinnedMessage(messageId)
writableDatabase
.delete(TABLE_NAME)
@@ -3764,6 +3766,62 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
}
}
/**
* When a message gets deleted, clear the pinned record and remove any references
*/
fun disassociatePinnedMessage(messageId: Long) {
if (messageId == -1L) {
return
}
writableDatabase.withinTransaction { db ->
// Clear pinned message info
val updated = db.update(TABLE_NAME)
.values(
PINNED_AT to 0,
PINNED_UNTIL to 0
)
.where("$ID = ? AND $PINNED_UNTIL > 0", messageId)
.run() > 0
if (!updated) {
return@withinTransaction
}
// Find the pinned message chat update
val pinningMessageId = db
.select(PINNING_MESSAGE_ID)
.from(TABLE_NAME)
.where("$ID = ?", messageId)
.run()
.readToSingleInt(-1)
if (pinningMessageId == -1) {
return@withinTransaction
}
// Disassociate chat update from pinned message
val messageExtras = db
.select(MESSAGE_EXTRAS)
.from(TABLE_NAME)
.where("$ID = ?", pinningMessageId)
.run()
.readToSingleObject { cursor ->
val messageExtraBytes = cursor.requireBlob(MESSAGE_EXTRAS)
messageExtraBytes?.let { MessageExtras.ADAPTER.decode(it) }
}
if (messageExtras?.pinnedMessage != null) {
val updatedMessageExtras = messageExtras.newBuilder().pinnedMessage(pinnedMessage = messageExtras.pinnedMessage.copy(pinnedMessageId = -1)).build()
db
.update(TABLE_NAME)
.values(MESSAGE_EXTRAS to updatedMessageExtras.encode())
.where("$ID = ?", pinningMessageId)
.run()
}
}
}
fun getSerializedSharedContacts(insertedAttachmentIds: Map<Attachment, AttachmentId>, contacts: List<Contact>): String? {
if (contacts.isEmpty()) {
return null

View File

@@ -41,6 +41,7 @@ import org.thoughtcrime.securesms.database.model.StoryType
import org.thoughtcrime.securesms.database.model.databaseprotos.BodyRangeList
import org.thoughtcrime.securesms.database.model.databaseprotos.GiftBadge
import org.thoughtcrime.securesms.database.model.databaseprotos.MessageExtras
import org.thoughtcrime.securesms.database.model.databaseprotos.PinnedMessage
import org.thoughtcrime.securesms.database.model.databaseprotos.PollTerminate
import org.thoughtcrime.securesms.database.model.toBodyRangeList
import org.thoughtcrime.securesms.dependencies.AppDependencies
@@ -251,6 +252,11 @@ object SyncMessageProcessor {
threadId = SignalDatabase.threads.getOrCreateThreadIdFor(getSyncMessageDestination(sent))
}
dataMessage.pollTerminate != null -> threadId = handleSynchronizedPollEnd(envelope, dataMessage, sent, senderRecipient, earlyMessageCacheEntry)
dataMessage.pinMessage != null -> threadId = handleSynchronizedPinMessage(envelope, dataMessage, sent, senderRecipient, earlyMessageCacheEntry)
dataMessage.unpinMessage != null -> {
DataMessageProcessor.handleUnpinMessage(envelope, dataMessage, senderRecipient, threadRecipient, earlyMessageCacheEntry)
threadId = SignalDatabase.threads.getOrCreateThreadIdFor(getSyncMessageDestination(sent))
}
else -> threadId = handleSynchronizeSentTextMessage(sent, envelope.timestamp!!)
}
@@ -1857,6 +1863,59 @@ object SyncMessageProcessor {
return threadId
}
private fun handleSynchronizedPinMessage(
envelope: Envelope,
message: DataMessage,
sent: Sent,
senderRecipient: Recipient,
earlyMessageCacheEntry: EarlyMessageCacheEntry?
): Long {
if (!RemoteConfig.receivePinnedMessages) {
log(envelope.timestamp!!, "Sync pinned messages not allowed due to remote config.")
}
log(envelope.timestamp!!, "Synchronize pinned message")
val recipient = getSyncMessageDestination(sent)
val threadId = SignalDatabase.threads.getOrCreateThreadIdFor(recipient)
val expiresInMillis = message.expireTimerDuration.inWholeMilliseconds
if (recipient.expiresInSeconds != message.expireTimerDuration.inWholeSeconds.toInt() || ((message.expireTimerVersion ?: -1) > recipient.expireTimerVersion)) {
handleSynchronizeSentExpirationUpdate(sent, sideEffect = true)
}
val pinMessage = message.pinMessage!!
val targetMessage = SignalDatabase.messages.getMessageFor(pinMessage.targetSentTimestamp!!, Recipient.self().id)
if (targetMessage == null) {
warn(envelope.timestamp!!, "Unable to find target message for sync message. Putting in early message cache.")
if (earlyMessageCacheEntry != null) {
AppDependencies.earlyMessageCache.store(senderRecipient.id, pinMessage.targetSentTimestamp!!, earlyMessageCacheEntry)
PushProcessEarlyMessagesJob.enqueue()
}
return -1
}
val duration = if (pinMessage.pinDurationForever == true) MessageTable.PIN_FOREVER else pinMessage.pinDurationSeconds!!.toLong()
val outgoingMessage = OutgoingMessage.pinMessage(
threadRecipient = recipient,
sentTimeMillis = sent.timestamp!!,
expiresIn = recipient.expiresInSeconds.seconds.inWholeMilliseconds,
messageExtras = MessageExtras(pinnedMessage = PinnedMessage(pinnedMessageId = targetMessage.id, targetAuthorAci = pinMessage.targetAuthorAciBinary!!, targetTimestamp = pinMessage.targetSentTimestamp!!, pinDurationInSeconds = duration))
)
val messageId = SignalDatabase.messages.insertMessageOutbox(outgoingMessage, threadId, false, GroupReceiptTable.STATUS_UNKNOWN, null).messageId
SignalDatabase.messages.markAsSent(messageId, true)
log(envelope.timestamp!!, "Inserted sync pin message as messageId $messageId")
if (expiresInMillis > 0) {
SignalDatabase.messages.markExpireStarted(messageId, sent.expirationStartTimestamp ?: 0)
AppDependencies.expiringMessageManager.scheduleDeletion(messageId, recipient.isGroup, sent.expirationStartTimestamp ?: 0, expiresInMillis)
}
return threadId
}
private fun ConversationIdentifier.toRecipientId(): RecipientId? {
val threadServiceId = ServiceId.parseOrNull(this.threadServiceId, this.threadServiceIdBinary)
return when {