feat: add broken unreads everywhere

This commit is contained in:
0x330a 2023-02-17 17:51:28 +11:00
parent 2f2ebe9451
commit 66fb6b30e9
No known key found for this signature in database
GPG Key ID: 267811D6E6A2698C
15 changed files with 123 additions and 115 deletions

View File

@ -3,18 +3,31 @@ package org.thoughtcrime.securesms.conversation.v2
import android.Manifest import android.Manifest
import android.animation.FloatEvaluator import android.animation.FloatEvaluator
import android.animation.ValueAnimator import android.animation.ValueAnimator
import android.content.* import android.content.ClipData
import android.content.ClipboardManager
import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.res.Resources import android.content.res.Resources
import android.database.Cursor import android.database.Cursor
import android.graphics.Rect import android.graphics.Rect
import android.graphics.Typeface import android.graphics.Typeface
import android.net.Uri import android.net.Uri
import android.os.* import android.os.AsyncTask
import android.os.Build
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.MediaStore import android.provider.MediaStore
import android.text.TextUtils import android.text.TextUtils
import android.util.Pair import android.util.Pair
import android.util.TypedValue import android.util.TypedValue
import android.view.* import android.view.ActionMode
import android.view.Menu
import android.view.MenuItem
import android.view.MotionEvent
import android.view.View
import android.view.WindowManager
import android.widget.LinearLayout import android.widget.LinearLayout
import android.widget.RelativeLayout import android.widget.RelativeLayout
import android.widget.Toast import android.widget.Toast
@ -57,8 +70,12 @@ import org.session.libsession.messaging.sending_receiving.attachments.Attachment
import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview import org.session.libsession.messaging.sending_receiving.link_preview.LinkPreview
import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel import org.session.libsession.messaging.sending_receiving.quotes.QuoteModel
import org.session.libsession.messaging.utilities.SessionId import org.session.libsession.messaging.utilities.SessionId
import org.session.libsession.utilities.* import org.session.libsession.utilities.Address
import org.session.libsession.utilities.Address.Companion.fromSerialized import org.session.libsession.utilities.Address.Companion.fromSerialized
import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.MediaTypes
import org.session.libsession.utilities.Stub
import org.session.libsession.utilities.TextSecurePreferences
import org.session.libsession.utilities.concurrent.SimpleTask import org.session.libsession.utilities.concurrent.SimpleTask
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
import org.session.libsession.utilities.recipients.RecipientModifiedListener import org.session.libsession.utilities.recipients.RecipientModifiedListener
@ -91,10 +108,25 @@ import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageView
import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate import org.thoughtcrime.securesms.conversation.v2.messages.VisibleMessageViewDelegate
import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar import org.thoughtcrime.securesms.conversation.v2.search.SearchBottomBar
import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel import org.thoughtcrime.securesms.conversation.v2.search.SearchViewModel
import org.thoughtcrime.securesms.conversation.v2.utilities.* import org.thoughtcrime.securesms.conversation.v2.utilities.AttachmentManager
import org.thoughtcrime.securesms.conversation.v2.utilities.BaseDialog
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionManagerUtilities
import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities
import org.thoughtcrime.securesms.conversation.v2.utilities.ResendMessageUtilities
import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.crypto.IdentityKeyUtil
import org.thoughtcrime.securesms.crypto.MnemonicUtilities import org.thoughtcrime.securesms.crypto.MnemonicUtilities
import org.thoughtcrime.securesms.database.* import org.thoughtcrime.securesms.database.GroupDatabase
import org.thoughtcrime.securesms.database.LokiAPIDatabase
import org.thoughtcrime.securesms.database.LokiMessageDatabase
import org.thoughtcrime.securesms.database.LokiThreadDatabase
import org.thoughtcrime.securesms.database.MmsDatabase
import org.thoughtcrime.securesms.database.MmsSmsDatabase
import org.thoughtcrime.securesms.database.ReactionDatabase
import org.thoughtcrime.securesms.database.RecipientDatabase
import org.thoughtcrime.securesms.database.SessionContactDatabase
import org.thoughtcrime.securesms.database.SmsDatabase
import org.thoughtcrime.securesms.database.Storage
import org.thoughtcrime.securesms.database.ThreadDatabase
import org.thoughtcrime.securesms.database.model.MessageId import org.thoughtcrime.securesms.database.model.MessageId
import org.thoughtcrime.securesms.database.model.MessageRecord import org.thoughtcrime.securesms.database.model.MessageRecord
import org.thoughtcrime.securesms.database.model.MmsMessageRecord import org.thoughtcrime.securesms.database.model.MmsMessageRecord
@ -107,12 +139,25 @@ import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel
import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState import org.thoughtcrime.securesms.linkpreview.LinkPreviewViewModel.LinkPreviewState
import org.thoughtcrime.securesms.mediasend.Media import org.thoughtcrime.securesms.mediasend.Media
import org.thoughtcrime.securesms.mediasend.MediaSendActivity import org.thoughtcrime.securesms.mediasend.MediaSendActivity
import org.thoughtcrime.securesms.mms.* import org.thoughtcrime.securesms.mms.AudioSlide
import org.thoughtcrime.securesms.mms.GifSlide
import org.thoughtcrime.securesms.mms.GlideApp
import org.thoughtcrime.securesms.mms.ImageSlide
import org.thoughtcrime.securesms.mms.MediaConstraints
import org.thoughtcrime.securesms.mms.Slide
import org.thoughtcrime.securesms.mms.SlideDeck
import org.thoughtcrime.securesms.mms.VideoSlide
import org.thoughtcrime.securesms.permissions.Permissions import org.thoughtcrime.securesms.permissions.Permissions
import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment import org.thoughtcrime.securesms.reactions.ReactionsDialogFragment
import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment import org.thoughtcrime.securesms.reactions.any.ReactWithAnyEmojiDialogFragment
import org.thoughtcrime.securesms.util.* import org.thoughtcrime.securesms.util.ActivityDispatcher
import java.util.* import org.thoughtcrime.securesms.util.ConfigurationMessageUtilities
import org.thoughtcrime.securesms.util.DateUtils
import org.thoughtcrime.securesms.util.MediaUtil
import org.thoughtcrime.securesms.util.SaveAttachmentTask
import org.thoughtcrime.securesms.util.push
import org.thoughtcrime.securesms.util.toPx
import java.util.Locale
import java.util.concurrent.ExecutionException import java.util.concurrent.ExecutionException
import java.util.concurrent.atomic.AtomicLong import java.util.concurrent.atomic.AtomicLong
import java.util.concurrent.atomic.AtomicReference import java.util.concurrent.atomic.AtomicReference
@ -363,10 +408,9 @@ class ConversationActivityV2 : PassphraseRequiredActionBarActivity(), InputBarDe
override fun onResume() { override fun onResume() {
super.onResume() super.onResume()
ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(viewModel.threadId) ApplicationContext.getInstance(this).messageNotifier.setVisibleThread(viewModel.threadId)
val recipient = viewModel.recipient ?: return
lifecycleScope.launch(Dispatchers.IO) { lifecycleScope.launch(Dispatchers.IO) {
threadDb.markAllAsRead(viewModel.threadId, recipient.isOpenGroupRecipient) storage.markConversationAsRead(viewModel.threadId, System.currentTimeMillis())
} }
contentResolver.registerContentObserver( contentResolver.registerContentObserver(

View File

@ -35,7 +35,7 @@ class ConfigDatabase(context: Context, helper: SQLCipherOpenHelper): Database(co
fun retrieveConfigAndHashes(variant: String, publicKey: String): Pair<ByteArray,List<String>>? { fun retrieveConfigAndHashes(variant: String, publicKey: String): Pair<ByteArray,List<String>>? {
val db = readableDatabase val db = readableDatabase
val query = db.query(TABLE_NAME, arrayOf(DATA), VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey),null, null, null) val query = db.query(TABLE_NAME, arrayOf(DATA, COMBINED_MESSAGE_HASHES), VARIANT_AND_PUBKEY_WHERE, arrayOf(variant, publicKey),null, null, null)
return query?.use { cursor -> return query?.use { cursor ->
if (!cursor.moveToFirst()) return@use null if (!cursor.moveToFirst()) return@use null
val bytes = cursor.getBlobOrNull(cursor.getColumnIndex(DATA)) ?: return@use null val bytes = cursor.getBlobOrNull(cursor.getColumnIndex(DATA)) ?: return@use null

View File

@ -366,10 +366,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
val attachmentDatabase = get(context).attachmentDatabase() val attachmentDatabase = get(context).attachmentDatabase()
queue(Runnable { attachmentDatabase.deleteAttachmentsForMessage(messageId) }) queue(Runnable { attachmentDatabase.deleteAttachmentsForMessage(messageId) })
val threadId = getThreadIdForMessage(messageId) val threadId = getThreadIdForMessage(messageId)
if (!read) {
val mentionChange = if (hasMention) { 1 } else { 0 }
get(context).threadDatabase().decrementUnread(threadId, 1, mentionChange)
}
updateMailboxBitmask( updateMailboxBitmask(
messageId, messageId,
MmsSmsColumns.Types.BASE_TYPE_MASK, MmsSmsColumns.Types.BASE_TYPE_MASK,
@ -634,7 +631,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
contentLocation: String, contentLocation: String,
threadId: Long, mailbox: Long, threadId: Long, mailbox: Long,
serverTimestamp: Long, serverTimestamp: Long,
runIncrement: Boolean,
runThreadUpdate: Boolean runThreadUpdate: Boolean
): Optional<InsertResult> { ): Optional<InsertResult> {
var threadId = threadId var threadId = threadId
@ -699,10 +695,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
null, null,
) )
if (!MmsSmsColumns.Types.isExpirationTimerUpdate(mailbox)) { if (!MmsSmsColumns.Types.isExpirationTimerUpdate(mailbox)) {
if (runIncrement) {
val mentionAmount = if (retrieved.hasMention()) { 1 } else { 0 }
get(context).threadDatabase().incrementUnread(threadId, 1, mentionAmount)
}
if (runThreadUpdate) { if (runThreadUpdate) {
get(context).threadDatabase().update(threadId, true) get(context).threadDatabase().update(threadId, true)
} }
@ -753,7 +745,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
retrieved: IncomingMediaMessage, retrieved: IncomingMediaMessage,
threadId: Long, threadId: Long,
serverTimestamp: Long = 0, serverTimestamp: Long = 0,
runIncrement: Boolean,
runThreadUpdate: Boolean runThreadUpdate: Boolean
): Optional<InsertResult> { ): Optional<InsertResult> {
var type = MmsSmsColumns.Types.BASE_INBOX_TYPE or MmsSmsColumns.Types.SECURE_MESSAGE_BIT var type = MmsSmsColumns.Types.BASE_INBOX_TYPE or MmsSmsColumns.Types.SECURE_MESSAGE_BIT
@ -772,7 +763,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
if (retrieved.isMessageRequestResponse) { if (retrieved.isMessageRequestResponse) {
type = type or MmsSmsColumns.Types.MESSAGE_REQUEST_RESPONSE_BIT type = type or MmsSmsColumns.Types.MESSAGE_REQUEST_RESPONSE_BIT
} }
return insertMessageInbox(retrieved, "", threadId, type, serverTimestamp, runIncrement, runThreadUpdate) return insertMessageInbox(retrieved, "", threadId, type, serverTimestamp, runThreadUpdate)
} }
@JvmOverloads @JvmOverloads

View File

@ -218,10 +218,6 @@ public class SmsDatabase extends MessagingDatabase {
contentValues.put(BODY, ""); contentValues.put(BODY, "");
contentValues.put(HAS_MENTION, 0); contentValues.put(HAS_MENTION, 0);
database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)}); database.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(messageId)});
long threadId = getThreadIdForMessage(messageId);
if (!read) {
DatabaseComponent.get(context).threadDatabase().decrementUnread(threadId, 1, (hasMention ? 1 : 0));
}
updateTypeBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_DELETED_TYPE); updateTypeBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_DELETED_TYPE);
} }
@ -394,7 +390,7 @@ public class SmsDatabase extends MessagingDatabase {
return new Pair<>(messageId, threadId); return new Pair<>(messageId, threadId);
} }
protected Optional<InsertResult> insertMessageInbox(IncomingTextMessage message, long type, long serverTimestamp, boolean runIncrement, boolean runThreadUpdate) { protected Optional<InsertResult> insertMessageInbox(IncomingTextMessage message, long type, long serverTimestamp, boolean runThreadUpdate) {
if (message.isSecureMessage()) { if (message.isSecureMessage()) {
type |= Types.SECURE_MESSAGE_BIT; type |= Types.SECURE_MESSAGE_BIT;
} else if (message.isGroup()) { } else if (message.isGroup()) {
@ -473,10 +469,6 @@ public class SmsDatabase extends MessagingDatabase {
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
long messageId = db.insert(TABLE_NAME, null, values); long messageId = db.insert(TABLE_NAME, null, values);
if (unread && runIncrement) {
DatabaseComponent.get(context).threadDatabase().incrementUnread(threadId, 1, (message.hasMention() ? 1 : 0));
}
if (runThreadUpdate) { if (runThreadUpdate) {
DatabaseComponent.get(context).threadDatabase().update(threadId, true); DatabaseComponent.get(context).threadDatabase().update(threadId, true);
} }
@ -491,16 +483,16 @@ public class SmsDatabase extends MessagingDatabase {
} }
} }
public Optional<InsertResult> insertMessageInbox(IncomingTextMessage message, boolean runIncrement, boolean runThreadUpdate) { public Optional<InsertResult> insertMessageInbox(IncomingTextMessage message, boolean runThreadUpdate) {
return insertMessageInbox(message, Types.BASE_INBOX_TYPE, 0, runIncrement, runThreadUpdate); return insertMessageInbox(message, Types.BASE_INBOX_TYPE, 0, runThreadUpdate);
} }
public Optional<InsertResult> insertCallMessage(IncomingTextMessage message) { public Optional<InsertResult> insertCallMessage(IncomingTextMessage message) {
return insertMessageInbox(message, 0, 0, true, true); return insertMessageInbox(message, 0, 0, true);
} }
public Optional<InsertResult> insertMessageInbox(IncomingTextMessage message, long serverTimestamp, boolean runIncrement, boolean runThreadUpdate) { public Optional<InsertResult> insertMessageInbox(IncomingTextMessage message, long serverTimestamp, boolean runThreadUpdate) {
return insertMessageInbox(message, Types.BASE_INBOX_TYPE, serverTimestamp, runIncrement, runThreadUpdate); return insertMessageInbox(message, Types.BASE_INBOX_TYPE, serverTimestamp, runThreadUpdate);
} }
public Optional<InsertResult> insertMessageOutbox(long threadId, OutgoingTextMessage message, long serverTimestamp, boolean runThreadUpdate) { public Optional<InsertResult> insertMessageOutbox(long threadId, OutgoingTextMessage message, long serverTimestamp, boolean runThreadUpdate) {

View File

@ -121,14 +121,16 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
return database.getAttachmentsForMessage(messageID) return database.getAttachmentsForMessage(messageID)
} }
override fun markConversationAsRead(threadId: Long, updateLastSeen: Boolean) { override fun getLastSeen(threadId: Long): Long {
val threadDb = DatabaseComponent.get(context).threadDatabase() val threadDb = DatabaseComponent.get(context).threadDatabase()
threadDb.setRead(threadId, updateLastSeen) return threadDb.getLastSeenAndHasSent(threadId)?.first() ?: 0L
} }
override fun incrementUnread(threadId: Long, amount: Int, unreadMentionAmount: Int) { override fun markConversationAsRead(threadId: Long, lastSeenTime: Long) {
val threadDb = DatabaseComponent.get(context).threadDatabase() val threadDb = DatabaseComponent.get(context).threadDatabase()
threadDb.incrementUnread(threadId, amount, unreadMentionAmount) getRecipientForThread(threadId)?.let {
threadDb.markAllAsRead(threadId, it.isGroupRecipient, lastSeenTime)
}
} }
override fun updateThread(threadId: Long, unarchive: Boolean) { override fun updateThread(threadId: Long, unarchive: Boolean) {
@ -142,7 +144,6 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
groupPublicKey: String?, groupPublicKey: String?,
openGroupID: String?, openGroupID: String?,
attachments: List<Attachment>, attachments: List<Attachment>,
runIncrement: Boolean,
runThreadUpdate: Boolean): Long? { runThreadUpdate: Boolean): Long? {
var messageID: Long? = null var messageID: Long? = null
val senderAddress = fromSerialized(message.sender!!) val senderAddress = fromSerialized(message.sender!!)
@ -189,7 +190,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
it.toSignalPointer() it.toSignalPointer()
} }
val mediaMessage = IncomingMediaMessage.from(message, senderAddress, targetRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews) val mediaMessage = IncomingMediaMessage.from(message, senderAddress, targetRecipient.expireMessages * 1000L, group, signalServiceAttachments, quote, linkPreviews)
mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.receivedTimestamp ?: 0, runIncrement, runThreadUpdate) mmsDatabase.insertSecureDecryptedMessageInbox(mediaMessage, message.threadID ?: -1, message.receivedTimestamp ?: 0, runThreadUpdate)
} }
if (insertResult.isPresent) { if (insertResult.isPresent) {
messageID = insertResult.get().messageId messageID = insertResult.get().messageId
@ -206,7 +207,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
val textMessage = if (isOpenGroupInvitation) IncomingTextMessage.fromOpenGroupInvitation(message.openGroupInvitation, senderAddress, message.sentTimestamp) val textMessage = if (isOpenGroupInvitation) IncomingTextMessage.fromOpenGroupInvitation(message.openGroupInvitation, senderAddress, message.sentTimestamp)
else IncomingTextMessage.from(message, senderAddress, group, targetRecipient.expireMessages * 1000L) else IncomingTextMessage.from(message, senderAddress, group, targetRecipient.expireMessages * 1000L)
val encrypted = IncomingEncryptedMessage(textMessage, textMessage.messageBody) val encrypted = IncomingEncryptedMessage(textMessage, textMessage.messageBody)
smsDatabase.insertMessageInbox(encrypted, message.receivedTimestamp ?: 0, runIncrement, runThreadUpdate) smsDatabase.insertMessageInbox(encrypted, message.receivedTimestamp ?: 0, runThreadUpdate)
} }
insertResult.orNull()?.let { result -> insertResult.orNull()?.let { result ->
messageID = result.messageId messageID = result.messageId
@ -344,6 +345,10 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
} }
} }
Log.d("Loki-DBG", "Should update thread $threadId") Log.d("Loki-DBG", "Should update thread $threadId")
if (threadId >= 0) {
DatabaseComponent.get(context).threadDatabase()
.setLastSeen(threadId, conversation.lastRead)
}
} }
} }
@ -578,7 +583,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON() val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON()
val infoMessage = IncomingGroupMessage(m, groupID, updateData, true) val infoMessage = IncomingGroupMessage(m, groupID, updateData, true)
val smsDB = DatabaseComponent.get(context).smsDatabase() val smsDB = DatabaseComponent.get(context).smsDatabase()
smsDB.insertMessageInbox(infoMessage, true, true) smsDB.insertMessageInbox(infoMessage, true)
} }
override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long) { override fun insertOutgoingInfoMessage(context: Context, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, threadID: Long, sentTimestamp: Long) {
@ -866,7 +871,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
Optional.of(message) Optional.of(message)
) )
database.insertSecureDecryptedMessageInbox(mediaMessage, -1, runIncrement = true, runThreadUpdate = true) database.insertSecureDecryptedMessageInbox(mediaMessage, -1, runThreadUpdate = true)
} }
override fun insertMessageRequestResponse(response: MessageRequestResponse) { override fun insertMessageRequestResponse(response: MessageRequestResponse) {
@ -959,7 +964,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper, private val configF
Optional.absent(), Optional.absent(),
Optional.absent() Optional.absent()
) )
mmsDb.insertSecureDecryptedMessageInbox(message, threadId, runIncrement = true, runThreadUpdate = true) mmsDb.insertSecureDecryptedMessageInbox(message, threadId, runThreadUpdate = true)
} }
} }

View File

@ -35,6 +35,7 @@ import com.annimon.stream.Stream;
import net.zetetic.database.sqlcipher.SQLiteDatabase; import net.zetetic.database.sqlcipher.SQLiteDatabase;
import org.jetbrains.annotations.NotNull; import org.jetbrains.annotations.NotNull;
import org.session.libsession.snode.SnodeAPI;
import org.session.libsession.utilities.Address; import org.session.libsession.utilities.Address;
import org.session.libsession.utilities.Contact; import org.session.libsession.utilities.Contact;
import org.session.libsession.utilities.DelimiterUtil; import org.session.libsession.utilities.DelimiterUtil;
@ -101,6 +102,8 @@ public class ThreadDatabase extends Database {
public static final String HAS_SENT = "has_sent"; public static final String HAS_SENT = "has_sent";
public static final String IS_PINNED = "is_pinned"; public static final String IS_PINNED = "is_pinned";
public static final String LAST_SEEN_TRIGGER = "thread_last_seen_trigger";
public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" + public static final String CREATE_TABLE = "CREATE TABLE " + TABLE_NAME + " (" +
ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " + ID + " INTEGER PRIMARY KEY, " + DATE + " INTEGER DEFAULT 0, " +
MESSAGE_COUNT + " INTEGER DEFAULT 0, " + ADDRESS + " TEXT, " + SNIPPET + " TEXT, " + MESSAGE_COUNT + " INTEGER DEFAULT 0, " + ADDRESS + " TEXT, " + SNIPPET + " TEXT, " +
@ -296,18 +299,17 @@ public class ThreadDatabase extends Database {
} }
public List<MarkedMessageInfo> setRead(long threadId, long lastReadTime) { public List<MarkedMessageInfo> setRead(long threadId, long lastReadTime) {
final List<MarkedMessageInfo> smsRecords = DatabaseComponent.get(context).smsDatabase().setMessagesRead(threadId, lastReadTime);
final List<MarkedMessageInfo> mmsRecords = DatabaseComponent.get(context).mmsDatabase().setMessagesRead(threadId, lastReadTime);
ContentValues contentValues = new ContentValues(1); ContentValues contentValues = new ContentValues(1);
contentValues.put(READ, 1); contentValues.put(READ, 1);
contentValues.put(UNREAD_COUNT, 0);
contentValues.put(UNREAD_MENTION_COUNT, 0);
contentValues.put(LAST_SEEN, lastReadTime); contentValues.put(LAST_SEEN, lastReadTime);
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId+""}); db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {threadId+""});
final List<MarkedMessageInfo> smsRecords = DatabaseComponent.get(context).smsDatabase().setMessagesRead(threadId, lastReadTime);
final List<MarkedMessageInfo> mmsRecords = DatabaseComponent.get(context).mmsDatabase().setMessagesRead(threadId, lastReadTime);
notifyConversationListListeners(); notifyConversationListListeners();
return new LinkedList<MarkedMessageInfo>() {{ return new LinkedList<MarkedMessageInfo>() {{
@ -340,30 +342,6 @@ public class ThreadDatabase extends Database {
}}; }};
} }
public void incrementUnread(long threadId, int amount, int unreadMentionAmount) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " +
UNREAD_COUNT + " = " + UNREAD_COUNT + " + ?, " +
UNREAD_MENTION_COUNT + " = " + UNREAD_MENTION_COUNT + " + ? WHERE " + ID + " = ?",
new String[] {
String.valueOf(amount),
String.valueOf(unreadMentionAmount),
String.valueOf(threadId)
});
}
public void decrementUnread(long threadId, int amount, int unreadMentionAmount) {
SQLiteDatabase db = databaseHelper.getWritableDatabase();
db.execSQL("UPDATE " + TABLE_NAME + " SET " + READ + " = 0, " +
UNREAD_COUNT + " = " + UNREAD_COUNT + " - ?, " +
UNREAD_MENTION_COUNT + " = " + UNREAD_MENTION_COUNT + " - ? WHERE " + ID + " = ? AND " + UNREAD_COUNT + " > 0",
new String[] {
String.valueOf(amount),
String.valueOf(unreadMentionAmount),
String.valueOf(threadId)
});
}
public void setDistributionType(long threadId, int distributionType) { public void setDistributionType(long threadId, int distributionType) {
ContentValues contentValues = new ContentValues(1); ContentValues contentValues = new ContentValues(1);
contentValues.put(TYPE, distributionType); contentValues.put(TYPE, distributionType);
@ -541,13 +519,15 @@ public class ThreadDatabase extends Database {
public void setLastSeen(long threadId, long timestamp) { public void setLastSeen(long threadId, long timestamp) {
SQLiteDatabase db = databaseHelper.getWritableDatabase(); SQLiteDatabase db = databaseHelper.getWritableDatabase();
ContentValues contentValues = new ContentValues(1); ContentValues contentValues = new ContentValues(1);
if (timestamp == -1) { long lastSeenTime = timestamp == -1 ? SnodeAPI.INSTANCE.getNowWithOffset() : timestamp;
contentValues.put(LAST_SEEN, System.currentTimeMillis()); contentValues.put(LAST_SEEN, lastSeenTime);
} else { db.beginTransaction();
contentValues.put(LAST_SEEN, timestamp);
}
db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(threadId)}); db.update(TABLE_NAME, contentValues, ID_WHERE, new String[] {String.valueOf(threadId)});
String countSubQuery = "SELECT COUNT(*) FROM "+SmsDatabase.TABLE_NAME+" AS s INNER JOIN "+TABLE_NAME+" AS t ON s.thread_id = t._id WHERE t._id = ? AND s."+SmsDatabase.DATE_SENT+" > t."+LAST_SEEN+"";
String reflectUpdates = "UPDATE "+TABLE_NAME+" AS t SET "+UNREAD_COUNT+" = ("+countSubQuery+") WHERE t."+ID+" = ?";
db.query(reflectUpdates, new String[]{threadId+"", threadId+""});
db.setTransactionSuccessful();
db.endTransaction();
notifyConversationListListeners(); notifyConversationListListeners();
} }
@ -755,8 +735,8 @@ public class ThreadDatabase extends Database {
notifyConversationListeners(threadId); notifyConversationListeners(threadId);
} }
public void markAllAsRead(long threadId, boolean isGroupRecipient) { public void markAllAsRead(long threadId, boolean isGroupRecipient, long lastSeenTime) {
List<MarkedMessageInfo> messages = setRead(threadId, true); List<MarkedMessageInfo> messages = setRead(threadId, lastSeenTime);
if (isGroupRecipient) { if (isGroupRecipient) {
for (MarkedMessageInfo message: messages) { for (MarkedMessageInfo message: messages) {
MarkReadReceiver.scheduleDeletion(context, message.getExpirationInfo()); MarkReadReceiver.scheduleDeletion(context, message.getExpirationInfo());
@ -765,6 +745,7 @@ public class ThreadDatabase extends Database {
MarkReadReceiver.process(context, messages); MarkReadReceiver.process(context, messages);
} }
ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, false, 0); ApplicationContext.getInstance(context).messageNotifier.updateNotification(context, false, 0);
setLastSeen(threadId, lastSeenTime);
} }
private boolean deleteThreadOnEmpty(long threadId) { private boolean deleteThreadOnEmpty(long threadId) {

View File

@ -88,9 +88,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
private static final int lokiV39 = 60; private static final int lokiV39 = 60;
private static final int lokiV40 = 61; private static final int lokiV40 = 61;
private static final int lokiV41 = 62; private static final int lokiV41 = 62;
private static final int lokiV42 = 63;
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes // Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
private static final int DATABASE_VERSION = lokiV41; private static final int DATABASE_VERSION = lokiV42;
private static final int MIN_DATABASE_VERSION = lokiV7; private static final int MIN_DATABASE_VERSION = lokiV7;
private static final String CIPHER3_DATABASE_NAME = "signal.db"; private static final String CIPHER3_DATABASE_NAME = "signal.db";
public static final String DATABASE_NAME = "signal_v4.db"; public static final String DATABASE_NAME = "signal_v4.db";
@ -324,6 +325,7 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
executeStatements(db, ReactionDatabase.CREATE_INDEXS); executeStatements(db, ReactionDatabase.CREATE_INDEXS);
executeStatements(db, ReactionDatabase.CREATE_REACTION_TRIGGERS); executeStatements(db, ReactionDatabase.CREATE_REACTION_TRIGGERS);
// db.execSQL(ThreadDatabase.getCreateLastSeenTrigger());
} }
@Override @Override
@ -560,6 +562,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
db.execSQL(ConfigDatabase.CREATE_CONFIG_TABLE_COMMAND); db.execSQL(ConfigDatabase.CREATE_CONFIG_TABLE_COMMAND);
} }
if (oldVersion < lokiV42) {
// db.execSQL(ThreadDatabase.getCreateLastSeenTrigger());
}
db.setTransactionSuccessful(); db.setTransactionSuccessful();
} finally { } finally {
db.endTransaction(); db.endTransaction();

View File

@ -28,6 +28,7 @@ import network.loki.messenger.databinding.ViewMessageRequestBannerBinding
import org.greenrobot.eventbus.EventBus import org.greenrobot.eventbus.EventBus
import org.greenrobot.eventbus.Subscribe import org.greenrobot.eventbus.Subscribe
import org.greenrobot.eventbus.ThreadMode import org.greenrobot.eventbus.ThreadMode
import org.session.libsession.messaging.MessagingModuleConfiguration
import org.session.libsession.messaging.jobs.JobQueue import org.session.libsession.messaging.jobs.JobQueue
import org.session.libsession.messaging.sending_receiving.MessageSender import org.session.libsession.messaging.sending_receiving.MessageSender
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
@ -556,7 +557,7 @@ class HomeActivity : PassphraseRequiredActionBarActivity(),
private fun markAllAsRead(thread: ThreadRecord) { private fun markAllAsRead(thread: ThreadRecord) {
ThreadUtils.queue { ThreadUtils.queue {
threadDb.markAllAsRead(thread.threadId, thread.recipient.isOpenGroupRecipient) MessagingModuleConfiguration.shared.storage.markConversationAsRead(thread.threadId, System.currentTimeMillis())
} }
} }

View File

@ -120,7 +120,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
Optional.absent(), Optional.absent(),
Optional.absent()); Optional.absent());
//insert the timer update message //insert the timer update message
database.insertSecureDecryptedMessageInbox(mediaMessage, -1, true, true); database.insertSecureDecryptedMessageInbox(mediaMessage, -1, true);
//set the timer to the conversation //set the timer to the conversation
DatabaseComponent.get(context).recipientDatabase().setExpireMessages(recipient, duration); DatabaseComponent.get(context).recipientDatabase().setExpireMessages(recipient, duration);

View File

@ -7,8 +7,6 @@ import org.session.libsession.messaging.messages.signal.IncomingTextMessage
import org.session.libsession.messaging.messages.signal.OutgoingTextMessage import org.session.libsession.messaging.messages.signal.OutgoingTextMessage
import org.session.libsession.messaging.open_groups.OpenGroup import org.session.libsession.messaging.open_groups.OpenGroup
import org.session.libsession.messaging.open_groups.OpenGroupApi import org.session.libsession.messaging.open_groups.OpenGroupApi
import org.session.libsession.messaging.sending_receiving.notifications.PushNotificationAPI
import org.session.libsession.messaging.sending_receiving.pollers.ClosedGroupPollerV2
import org.session.libsession.utilities.Address import org.session.libsession.utilities.Address
import org.session.libsession.utilities.GroupUtil import org.session.libsession.utilities.GroupUtil
import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.Recipient
@ -21,7 +19,6 @@ import org.thoughtcrime.securesms.crypto.KeyPairUtilities
import org.thoughtcrime.securesms.dependencies.DatabaseComponent import org.thoughtcrime.securesms.dependencies.DatabaseComponent
import org.thoughtcrime.securesms.groups.GroupManager import org.thoughtcrime.securesms.groups.GroupManager
import java.security.SecureRandom import java.security.SecureRandom
import java.util.*
import kotlin.random.asKotlinRandom import kotlin.random.asKotlinRandom
object MockDataGenerator { object MockDataGenerator {
@ -139,7 +136,6 @@ object MockDataGenerator {
false false
), ),
(timestampNow - (index * 5000)), (timestampNow - (index * 5000)),
false,
false false
) )
} }
@ -269,7 +265,6 @@ object MockDataGenerator {
false false
), ),
(timestampNow - (index * 5000)), (timestampNow - (index * 5000)),
false,
false false
) )
} }
@ -395,7 +390,6 @@ object MockDataGenerator {
false false
), ),
(timestampNow - (index * 5000)), (timestampNow - (index * 5000)),
false,
false false
) )
} else { } else {

@ -1 +1 @@
Subproject commit 3fd0e0977d156a2e748a14aaa0c94da0bb71560e Subproject commit bbdc35d2ec8204c1a36bbe45714625d093b82602

View File

@ -177,9 +177,9 @@ interface StorageProtocol {
/** /**
* Returns the ID of the `TSIncomingMessage` that was constructed. * Returns the ID of the `TSIncomingMessage` that was constructed.
*/ */
fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List<LinkPreview?>, groupPublicKey: String?, openGroupID: String?, attachments: List<Attachment>, runIncrement: Boolean, runThreadUpdate: Boolean): Long? fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List<LinkPreview?>, groupPublicKey: String?, openGroupID: String?, attachments: List<Attachment>, runThreadUpdate: Boolean): Long?
fun markConversationAsRead(threadId: Long, updateLastSeen: Boolean) fun markConversationAsRead(threadId: Long, lastSeenTime: Long)
fun incrementUnread(threadId: Long, amount: Int, unreadMentionAmount: Int) fun getLastSeen(threadId: Long): Long
fun updateThread(threadId: Long, unarchive: Boolean) fun updateThread(threadId: Long, unarchive: Boolean)
fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long) fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long)
fun insertMessageRequestResponse(response: MessageRequestResponse) fun insertMessageRequestResponse(response: MessageRequestResponse)

View File

@ -123,7 +123,7 @@ class BatchMessageReceiveJob(
async { async {
// The LinkedHashMap should preserve insertion order // The LinkedHashMap should preserve insertion order
val messageIds = linkedMapOf<Long, Pair<Boolean, Boolean>>() val messageIds = linkedMapOf<Long, Pair<Boolean, Boolean>>()
var myLastSeen = storage.getLastSeen(threadId)
messages.forEach { (parameters, message, proto) -> messages.forEach { (parameters, message, proto) ->
try { try {
when (message) { when (message) {
@ -137,6 +137,12 @@ class BatchMessageReceiveJob(
if (messageId != null && message.reaction == null) { if (messageId != null && message.reaction == null) {
val isUserBlindedSender = message.sender == serverPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId( val isUserBlindedSender = message.sender == serverPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId(
IdPrefix.BLINDED, it.publicKey.asBytes).hexString } IdPrefix.BLINDED, it.publicKey.asBytes).hexString }
if (message.sender == localUserPublicKey || isUserBlindedSender) {
val sentTimestamp = message.sentTimestamp
if (sentTimestamp != null && sentTimestamp > myLastSeen) {
myLastSeen = sentTimestamp // use sent timestamp here since that is technically the last one we have
}
}
messageIds[messageId] = Pair( messageIds[messageId] = Pair(
(message.sender == localUserPublicKey || isUserBlindedSender), (message.sender == localUserPublicKey || isUserBlindedSender),
message.hasMention message.hasMention
@ -169,21 +175,8 @@ class BatchMessageReceiveJob(
} }
} }
// increment unreads, notify, and update thread // increment unreads, notify, and update thread
val unreadFromMine = messageIds.map { it.value.first }.indexOfLast { it } // last seen will be the current last seen if not changed (re-computes the read counts for thread record)
var trueUnreadCount = messageIds.filter { !it.value.first }.size storage.markConversationAsRead(threadId, 0)
var trueUnreadMentionCount = messageIds.filter { !it.value.first && it.value.second }.size
if (unreadFromMine >= 0) {
storage.markConversationAsRead(threadId, false)
val trueUnreadIds = messageIds.keys.toList().subList(unreadFromMine + 1, messageIds.keys.count())
trueUnreadCount = trueUnreadIds.size
trueUnreadMentionCount = messageIds
.filter { trueUnreadIds.contains(it.key) && !it.value.first && it.value.second }
.size
}
if (trueUnreadCount > 0) {
storage.incrementUnread(threadId, trueUnreadCount, trueUnreadMentionCount)
}
storage.updateThread(threadId, true) storage.updateThread(threadId, true)
SSKEnvironment.shared.notificationManager.updateNotification(context, threadId) SSKEnvironment.shared.notificationManager.updateNotification(context, threadId)
} }

View File

@ -66,7 +66,8 @@ object MessageSender {
return if (destination is Destination.LegacyOpenGroup || destination is Destination.OpenGroup || destination is Destination.OpenGroupInbox) { return if (destination is Destination.LegacyOpenGroup || destination is Destination.OpenGroup || destination is Destination.OpenGroupInbox) {
sendToOpenGroupDestination(destination, message) sendToOpenGroupDestination(destination, message)
} else { } else {
sendToSnodeDestination(destination, message) val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey()
sendToSnodeDestination(destination, message, destination is Destination.Contact && destination.publicKey == userPublicKey)
} }
} }

View File

@ -343,7 +343,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage,
message.threadID = threadID message.threadID = threadID
val messageID = val messageID =
storage.persist(message, quoteModel, linkPreviews, message.groupPublicKey, openGroupID, storage.persist(message, quoteModel, linkPreviews, message.groupPublicKey, openGroupID,
attachments, runIncrement, runThreadUpdate attachments, runThreadUpdate
) ?: return null ) ?: return null
val openGroupServerID = message.openGroupServerMessageID val openGroupServerID = message.openGroupServerMessageID
if (openGroupServerID != null) { if (openGroupServerID != null) {