Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ public final class ConversationListItem extends ConstraintLayout implements Bind
private View uncheckedView;
private View checkedView;
private View unreadMentions;
private View unreadReactions;
private View pinnedView;
private int thumbSize;
private GlideLiveDataTarget thumbTarget;
Expand Down Expand Up @@ -171,6 +172,7 @@ protected void onFinishInflate() {
this.uncheckedView = findViewById(R.id.conversation_list_item_unchecked);
this.checkedView = findViewById(R.id.conversation_list_item_checked);
this.unreadMentions = findViewById(R.id.conversation_list_item_unread_mentions_indicator);
this.unreadReactions = findViewById(R.id.conversation_list_item_unread_reactions_indicator);
this.thumbSize = (int) DimensionUnit.SP.toPixels(16f);
this.thumbTarget = new GlideLiveDataTarget(thumbSize, thumbSize);
this.searchStyleFactory = () -> new CharacterStyle[] { new ForegroundColorSpan(ContextCompat.getColor(getContext(), R.color.signal_colorOnSurface)), SpanUtil.getBoldSpan() };
Expand Down Expand Up @@ -324,6 +326,7 @@ public void bindMessage(@NonNull LifecycleOwner lifecycleOwner,
archivedView.setVisibility(GONE);
unreadIndicator.setVisibility(GONE);
unreadMentions.setVisibility(GONE);
unreadReactions.setVisibility(GONE);
deliveryStatusIndicator.setNone();
alertView.setNone();

Expand Down Expand Up @@ -362,6 +365,7 @@ public void bindGroupWithMembers(@NonNull LifecycleOwner lifecycleOwner,
archivedView.setVisibility(GONE);
unreadIndicator.setVisibility(GONE);
unreadMentions.setVisibility(GONE);
unreadReactions.setVisibility(GONE);
deliveryStatusIndicator.setNone();
alertView.setNone();

Expand Down Expand Up @@ -549,15 +553,30 @@ private void setUnreadIndicator(ThreadRecord thread) {
if (thread.isRead()) {
unreadIndicator.setVisibility(View.GONE);
unreadMentions.setVisibility(View.GONE);
unreadReactions.setVisibility(View.GONE);
return;
}

if (thread.getUnreadSelfMentionsCount() > 0) {
// we have mentions, show those and unread indicator if there are multiple unread messages
unreadMentions.setVisibility(View.VISIBLE);
unreadIndicator.setVisibility(thread.getUnreadCount() == 1 ? View.GONE : View.VISIBLE);
unreadReactions.setVisibility(View.GONE);
} else {
// no mentions, hide the mention indicator
unreadMentions.setVisibility(View.GONE);
unreadIndicator.setVisibility(View.VISIBLE);
// check for unread messages and reactions separately since either could cause
// the thread to be unread
if (thread.getUnreadCount() > 0) {
unreadIndicator.setVisibility(View.VISIBLE);
} else {
unreadIndicator.setVisibility(View.GONE);
}
if (thread.getUnreadReactionToSelfCount() > 0) {
unreadReactions.setVisibility(View.VISIBLE);
} else {
unreadReactions.setVisibility(View.GONE);
}
}

unreadIndicator.setText(unreadCount > 0 ? String.valueOf(unreadCount) : " ");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ import org.signal.core.util.insertInto
import org.signal.core.util.logging.Log
import org.signal.core.util.readToList
import org.signal.core.util.readToSet
import org.signal.core.util.readToSingleBoolean
import org.signal.core.util.readToSingleInt
import org.signal.core.util.readToSingleLong
import org.signal.core.util.readToSingleLongOrNull
Expand Down Expand Up @@ -951,7 +952,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
)

val messageId = MessageId(db.insert(TABLE_NAME, null, values))
threads.incrementUnread(threadId, 1, 0)
threads.incrementUnread(threadId, 1, 0, 0)
threads.update(threadId, true)

messageId
Expand Down Expand Up @@ -2501,6 +2502,15 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
.readToSingleInt()
}

fun getUnreadReactionsToSelfCount(threadId: Long): Int {
return readableDatabase
.count()
.from(TABLE_NAME)
.where("$THREAD_ID = ? AND $STORY_TYPE = 0 AND $PARENT_STORY_ID <= 0 AND $ORIGINAL_MESSAGE_ID IS NULL AND $SCHEDULED_DATE = -1 AND ($outgoingTypeClause) AND $REACTIONS_UNREAD = 1", threadId)
.run()
.readToSingleInt()
}

/**
* Trims data related to expired messages. Only intended to be run after a backup restore.
*/
Expand Down Expand Up @@ -2888,7 +2898,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
editedMessage == null
) {
val incrementUnreadMentions = retrieved.mentions.isNotEmpty() && retrieved.mentions.any { it.recipientId == Recipient.self().id }
threads.incrementUnread(threadId, 1, if (incrementUnreadMentions) 1 else 0)
threads.incrementUnread(threadId, 1, if (incrementUnreadMentions) 1 else 0, 0)
ThreadUpdateJob.enqueue(threadId)
}

Expand Down Expand Up @@ -2934,7 +2944,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
)
.run()

threads.incrementUnread(threadId, 1, 0)
threads.incrementUnread(threadId, 1, 0, 0)
threads.update(threadId, true)

notifyConversationListeners(threadId)
Expand Down Expand Up @@ -2963,7 +2973,7 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
)
.run()

threads.incrementUnread(threadId, 1, 0)
threads.incrementUnread(threadId, 1, 0, 0)
threads.update(threadId, true)

notifyConversationListeners(threadId)
Expand Down Expand Up @@ -5237,12 +5247,24 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat

fun updateReactionsUnread(db: SQLiteDatabase, messageId: Long, hasReactions: Boolean, isRemoval: Boolean) {
try {
val isOutgoing = getMessageRecord(messageId).isOutgoing
val messageRecord = getMessageRecord(messageId)
val isOutgoing = messageRecord.isOutgoing
val threadId = messageRecord.threadId
val hasUnreadReactions = readableDatabase
.select(REACTIONS_UNREAD)
.from(TABLE_NAME)
.where("$ID = ?", messageId)
.run()
.readToSingleBoolean()
val values = ContentValues()

if (!hasReactions) {
values.put(REACTIONS_UNREAD, 0)
} else if (!isRemoval) {
// increment unread reactions to self only on first reaction
if (!hasUnreadReactions && isOutgoing) {
threads.incrementUnread(threadId, 0, 0, 1)
}
values.put(REACTIONS_UNREAD, 1)
}

Expand All @@ -5256,6 +5278,10 @@ open class MessageTable(context: Context?, databaseHelper: SignalDatabase) : Dat
.where("$ID = ?", messageId)
.run()
}

ThreadUpdateJob.enqueue(threadId)
notifyConversationListeners(threadId)
notifyConversationListListeners()
} catch (e: NoSuchMessageException) {
Log.w(TAG, "Failed to find message $messageId")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
const val LAST_SCROLLED = "last_scrolled"
const val PINNED_ORDER = "pinned_order"
const val UNREAD_SELF_MENTION_COUNT = "unread_self_mention_count"
const val UNREAD_REACTION_TO_SELF_COUNT = "unread_reaction_to_self_count"
const val ACTIVE = "active"

const val MAX_CACHE_SIZE = 1000
Expand Down Expand Up @@ -149,6 +150,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
$LAST_SCROLLED INTEGER DEFAULT 0,
$PINNED_ORDER INTEGER UNIQUE DEFAULT NULL,
$UNREAD_SELF_MENTION_COUNT INTEGER DEFAULT 0,
$UNREAD_REACTION_TO_SELF_COUNT INTEGER DEFAULT 0,
$ACTIVE INTEGER DEFAULT 0,
$SNIPPET_MESSAGE_EXTRAS BLOB DEFAULT NULL,
$SNIPPET_MESSAGE_ID INTEGER DEFAULT 0
Expand Down Expand Up @@ -187,7 +189,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
HAS_READ_RECEIPT,
LAST_SCROLLED,
PINNED_ORDER,
UNREAD_SELF_MENTION_COUNT
UNREAD_SELF_MENTION_COUNT,
UNREAD_REACTION_TO_SELF_COUNT
)

private val TYPED_THREAD_PROJECTION: List<String> = THREAD_PROJECTION
Expand Down Expand Up @@ -240,6 +243,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
readReceiptCount: Int,
unreadCount: Int,
unreadMentionCount: Int,
unreadReactionToSelfCount: Int,
messageExtras: MessageExtras?
) {
var extraSerialized: String? = null
Expand Down Expand Up @@ -267,6 +271,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
ACTIVE to 1,
UNREAD_COUNT to unreadCount,
UNREAD_SELF_MENTION_COUNT to unreadMentionCount,
UNREAD_REACTION_TO_SELF_COUNT to unreadReactionToSelfCount,
SNIPPET_MESSAGE_EXTRAS to messageExtras?.encode(),
SNIPPET_MESSAGE_ID to messageId
)
Expand Down Expand Up @@ -473,7 +478,8 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
.values(
READ to ReadStatus.READ.serialize(),
UNREAD_COUNT to 0,
UNREAD_SELF_MENTION_COUNT to 0
UNREAD_SELF_MENTION_COUNT to 0,
UNREAD_REACTION_TO_SELF_COUNT to 0
)
.run()

Expand Down Expand Up @@ -563,12 +569,14 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa

val unreadCount = messages.getUnreadCount(threadId)
val unreadMentionsCount = messages.getUnreadMentionCount(threadId)
val unreadReactionToSelfCount = messages.getUnreadReactionsToSelfCount(threadId)
val lastSeenTimestamp = messages.getMostRecentReadMessageDateReceived(threadId) ?: System.currentTimeMillis()

val contentValues = contentValuesOf(
READ to ReadStatus.READ.serialize(),
UNREAD_COUNT to unreadCount,
UNREAD_SELF_MENTION_COUNT to unreadMentionsCount,
UNREAD_REACTION_TO_SELF_COUNT to unreadReactionToSelfCount,
LAST_SEEN to lastSeenTimestamp
)

Expand Down Expand Up @@ -770,17 +778,18 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
.use(mapCursorToType)
}

fun incrementUnread(threadId: Long, unreadAmount: Int, unreadSelfMentionAmount: Int) {
fun incrementUnread(threadId: Long, unreadAmount: Int, unreadSelfMentionAmount: Int, unreadReactionToSelfAmount: Int) {
writableDatabase.execSQL(
"""
UPDATE $TABLE_NAME
SET $READ = ${ReadStatus.UNREAD.serialize()},
$UNREAD_COUNT = $UNREAD_COUNT + ?,
$UNREAD_SELF_MENTION_COUNT = $UNREAD_SELF_MENTION_COUNT + ?,
$UNREAD_REACTION_TO_SELF_COUNT = $UNREAD_REACTION_TO_SELF_COUNT + ?,
$LAST_SCROLLED = ?
WHERE $ID = ?
""",
SqlUtil.buildArgs(unreadAmount, unreadSelfMentionAmount, 0, threadId)
SqlUtil.buildArgs(unreadAmount, unreadSelfMentionAmount, unreadReactionToSelfAmount, 0, threadId)
)
}

Expand Down Expand Up @@ -1562,13 +1571,15 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
val previous = getThreadRecord(threadId)
val unreadCount = messages.getUnreadCount(threadId)
val unreadMentionsCount = messages.getUnreadMentionCount(threadId)
val unreadReactionToSelfCount = messages.getUnreadReactionsToSelfCount(threadId)

writableDatabase
.update(TABLE_NAME)
.values(
READ to if (unreadCount == 0) ReadStatus.READ.serialize() else ReadStatus.UNREAD.serialize(),
UNREAD_COUNT to unreadCount,
UNREAD_SELF_MENTION_COUNT to unreadMentionsCount
UNREAD_SELF_MENTION_COUNT to unreadMentionsCount,
UNREAD_REACTION_TO_SELF_COUNT to unreadReactionToSelfCount
)
.where("$ID = ?", threadId)
.run()
Expand Down Expand Up @@ -1655,10 +1666,12 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
} else if (threadId != null) {
val unreadCount = messages.getUnreadCount(threadId)
val unreadMentionsCount = messages.getUnreadMentionCount(threadId)
val unreadReactionToSelfCount = messages.getUnreadReactionsToSelfCount(threadId)

values.put(READ, if (unreadCount == 0) ReadStatus.READ.serialize() else ReadStatus.UNREAD.serialize())
values.put(UNREAD_COUNT, unreadCount)
values.put(UNREAD_SELF_MENTION_COUNT, unreadMentionsCount)
values.put(UNREAD_REACTION_TO_SELF_COUNT, unreadReactionToSelfCount)
}

writableDatabase
Expand Down Expand Up @@ -1800,6 +1813,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
readReceiptCount = 0,
unreadCount = 0,
unreadMentionCount = 0,
unreadReactionToSelfCount = 0,
messageExtras = null
)
}
Expand All @@ -1813,6 +1827,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
val threadBody: ThreadBody = ThreadBodyUtil.getFormattedBodyFor(context, record)
val unreadCount: Int = messages.getUnreadCount(threadId)
val unreadMentionCount: Int = messages.getUnreadMentionCount(threadId)
val unreadReactionToSelfCount: Int = messages.getUnreadReactionsToSelfCount(threadId)

updateThread(
threadId = threadId,
Expand All @@ -1831,6 +1846,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
readReceiptCount = record.hasReadReceipt().toInt(),
unreadCount = unreadCount,
unreadMentionCount = unreadMentionCount,
unreadReactionToSelfCount = unreadReactionToSelfCount,
messageExtras = record.messageExtras
)

Expand Down Expand Up @@ -2011,6 +2027,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
LAST_SCROLLED to 0,
PINNED_ORDER to null,
UNREAD_SELF_MENTION_COUNT to 0,
UNREAD_REACTION_TO_SELF_COUNT to 0,
ACTIVE to 0
)

Expand Down Expand Up @@ -2314,6 +2331,7 @@ class ThreadTable(context: Context, databaseHelper: SignalDatabase) : DatabaseTa
.setForcedUnread(cursor.requireInt(READ) == ReadStatus.FORCED_UNREAD.serialize())
.setPinned(cursor.requireBoolean(PINNED_ORDER))
.setUnreadSelfMentionsCount(cursor.requireInt(UNREAD_SELF_MENTION_COUNT))
.setUnreadReactionToSelfCount(cursor.requireInt(UNREAD_REACTION_TO_SELF_COUNT))
.setExtra(extra)
.setSnippetMessageExtras(messageExtras)
.build()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -148,6 +148,7 @@ import org.thoughtcrime.securesms.database.helpers.migration.V290_AddArchiveThum
import org.thoughtcrime.securesms.database.helpers.migration.V291_NullOutRemoteKeyIfEmpty
import org.thoughtcrime.securesms.database.helpers.migration.V292_AddPollTables
import org.thoughtcrime.securesms.database.helpers.migration.V293_LastResortKeyTupleTableMigration
import org.thoughtcrime.securesms.database.helpers.migration.V294_ThreadUnreadReactionToSelfCount
import org.thoughtcrime.securesms.database.SQLiteDatabase as SignalSqliteDatabase

/**
Expand Down Expand Up @@ -301,10 +302,11 @@ object SignalDatabaseMigrations {
290 to V290_AddArchiveThumbnailTransferStateColumn,
291 to V291_NullOutRemoteKeyIfEmpty,
292 to V292_AddPollTables,
293 to V293_LastResortKeyTupleTableMigration
293 to V293_LastResortKeyTupleTableMigration,
294 to V294_ThreadUnreadReactionToSelfCount
)

const val DATABASE_VERSION = 293
const val DATABASE_VERSION = 294

@JvmStatic
fun migrate(context: Application, db: SignalSqliteDatabase, oldVersion: Int, newVersion: Int) {
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
/*
* Copyright 2025 Signal Messenger, LLC
* SPDX-License-Identifier: AGPL-3.0-only
*/

package org.thoughtcrime.securesms.database.helpers.migration

import android.app.Application
import org.thoughtcrime.securesms.database.SQLiteDatabase

@Suppress("ClassName")
object V294_ThreadUnreadReactionToSelfCount : SignalDatabaseMigration {
override fun migrate(context: Application, db: SQLiteDatabase, oldVersion: Int, newVersion: Int) {
db.execSQL("ALTER TABLE thread ADD COLUMN unread_reaction_to_self_count INTEGER DEFAULT 0")
}
}
Loading