mirror of
https://github.com/oxen-io/session-android.git
synced 2025-02-17 12:38:27 +00:00
Merge pull request #1082 from mpretty-cyro/feature/unread-mention-indicator
Added the unread mention indicator to the conversation list
This commit is contained in:
commit
abf733fbdb
@ -74,10 +74,10 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
|
||||
attachmentDatabase.setTransferState(messageID, attachmentId, attachmentState.value)
|
||||
}
|
||||
|
||||
override fun getMessageForQuote(timestamp: Long, author: Address): Pair<Long, Boolean>? {
|
||||
override fun getMessageForQuote(timestamp: Long, author: Address): Triple<Long, Boolean, String>? {
|
||||
val messagingDatabase = DatabaseComponent.get(context).mmsSmsDatabase()
|
||||
val message = messagingDatabase.getMessageFor(timestamp, author)
|
||||
return if (message != null) Pair(message.id, message.isMms) else null
|
||||
return if (message != null) Triple(message.id, message.isMms, message.body) else null
|
||||
}
|
||||
|
||||
override fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List<Attachment> {
|
||||
@ -198,16 +198,18 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
|
||||
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHashes(messageIDs)
|
||||
}
|
||||
|
||||
override fun updateMessageAsDeleted(timestamp: Long, author: String) {
|
||||
override fun updateMessageAsDeleted(timestamp: Long, author: String): Long? {
|
||||
val database = DatabaseComponent.get(context).mmsSmsDatabase()
|
||||
val address = Address.fromSerialized(author)
|
||||
val message = database.getMessageFor(timestamp, address) ?: return
|
||||
val message = database.getMessageFor(timestamp, address) ?: return null
|
||||
val messagingDatabase: MessagingDatabase = if (message.isMms) DatabaseComponent.get(context).mmsDatabase()
|
||||
else DatabaseComponent.get(context).smsDatabase()
|
||||
messagingDatabase.markAsDeleted(message.id, message.isRead)
|
||||
messagingDatabase.markAsDeleted(message.id, message.isRead, message.hasMention)
|
||||
if (message.isOutgoing) {
|
||||
messagingDatabase.deleteMessage(message.id)
|
||||
}
|
||||
|
||||
return message.id
|
||||
}
|
||||
|
||||
override fun getServerHashForMessage(messageID: Long): String? {
|
||||
|
@ -39,7 +39,7 @@ public abstract class MessagingDatabase extends Database implements MmsSmsColumn
|
||||
public abstract void markAsSent(long messageId, boolean secure);
|
||||
public abstract void markUnidentified(long messageId, boolean unidentified);
|
||||
|
||||
public abstract void markAsDeleted(long messageId, boolean read);
|
||||
public abstract void markAsDeleted(long messageId, boolean read, boolean hasMention);
|
||||
|
||||
public abstract boolean deleteMessage(long messageId);
|
||||
public abstract boolean deleteMessages(long[] messageId, long threadId);
|
||||
|
@ -356,17 +356,19 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
||||
db.update(TABLE_NAME, contentValues, ID_WHERE, arrayOf(messageId.toString()))
|
||||
}
|
||||
|
||||
override fun markAsDeleted(messageId: Long, read: Boolean) {
|
||||
override fun markAsDeleted(messageId: Long, read: Boolean, hasMention: Boolean) {
|
||||
val database = databaseHelper.writableDatabase
|
||||
val contentValues = ContentValues()
|
||||
contentValues.put(READ, 1)
|
||||
contentValues.put(BODY, "")
|
||||
contentValues.put(HAS_MENTION, 0)
|
||||
database.update(TABLE_NAME, contentValues, ID_WHERE, arrayOf(messageId.toString()))
|
||||
val attachmentDatabase = get(context).attachmentDatabase()
|
||||
queue(Runnable { attachmentDatabase.deleteAttachmentsForMessage(messageId) })
|
||||
val threadId = getThreadIdForMessage(messageId)
|
||||
if (!read) {
|
||||
get(context).threadDatabase().decrementUnread(threadId, 1)
|
||||
val mentionChange = if (hasMention) { 1 } else { 0 }
|
||||
get(context).threadDatabase().decrementUnread(threadId, 1, mentionChange)
|
||||
}
|
||||
updateMailboxBitmask(
|
||||
messageId,
|
||||
@ -659,6 +661,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
||||
contentValues.put(EXPIRES_IN, retrieved.expiresIn)
|
||||
contentValues.put(READ, if (retrieved.isExpirationUpdate) 1 else 0)
|
||||
contentValues.put(UNIDENTIFIED, retrieved.isUnidentified)
|
||||
contentValues.put(HAS_MENTION, retrieved.hasMention())
|
||||
contentValues.put(MESSAGE_REQUEST_RESPONSE, retrieved.isMessageRequestResponse)
|
||||
if (!contentValues.containsKey(DATE_SENT)) {
|
||||
contentValues.put(DATE_SENT, contentValues.getAsLong(DATE_RECEIVED))
|
||||
@ -690,7 +693,8 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
||||
)
|
||||
if (!MmsSmsColumns.Types.isExpirationTimerUpdate(mailbox)) {
|
||||
if (runIncrement) {
|
||||
get(context).threadDatabase().incrementUnread(threadId, 1)
|
||||
val mentionAmount = if (retrieved.hasMention()) { 1 } else { 0 }
|
||||
get(context).threadDatabase().incrementUnread(threadId, 1, mentionAmount)
|
||||
}
|
||||
if (runThreadUpdate) {
|
||||
get(context).threadDatabase().update(threadId, true)
|
||||
@ -1289,7 +1293,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
||||
message.outgoingQuote!!.missing,
|
||||
SlideDeck(context, message.outgoingQuote!!.attachments!!)
|
||||
) else null,
|
||||
message.sharedContacts, message.linkPreviews, listOf(), false
|
||||
message.sharedContacts, message.linkPreviews, listOf(), false, false
|
||||
)
|
||||
}
|
||||
|
||||
@ -1333,6 +1337,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
||||
)
|
||||
var readReceiptCount = cursor.getInt(cursor.getColumnIndexOrThrow(READ_RECEIPT_COUNT))
|
||||
val subscriptionId = cursor.getInt(cursor.getColumnIndexOrThrow(SUBSCRIPTION_ID))
|
||||
val hasMention = (cursor.getInt(cursor.getColumnIndexOrThrow(HAS_MENTION)) == 1)
|
||||
if (!isReadReceiptsEnabled(context)) {
|
||||
readReceiptCount = 0
|
||||
}
|
||||
@ -1350,7 +1355,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
||||
dateSent, dateReceived, deliveryReceiptCount, threadId,
|
||||
contentLocationBytes, messageSize, expiry, status,
|
||||
transactionIdBytes, mailbox, slideDeck,
|
||||
readReceiptCount
|
||||
readReceiptCount, hasMention
|
||||
)
|
||||
}
|
||||
|
||||
@ -1384,6 +1389,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
||||
val expiresIn = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRES_IN))
|
||||
val expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(EXPIRE_STARTED))
|
||||
val unidentified = cursor.getInt(cursor.getColumnIndexOrThrow(UNIDENTIFIED)) == 1
|
||||
val hasMention = cursor.getInt(cursor.getColumnIndexOrThrow(HAS_MENTION)) == 1
|
||||
if (!isReadReceiptsEnabled(context)) {
|
||||
readReceiptCount = 0
|
||||
}
|
||||
@ -1420,7 +1426,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
||||
addressDeviceId, dateSent, dateReceived, deliveryReceiptCount,
|
||||
threadId, body, slideDeck!!, partCount, box, mismatches,
|
||||
networkFailures, subscriptionId, expiresIn, expireStarted,
|
||||
readReceiptCount, quote, contacts, previews, reactions, unidentified
|
||||
readReceiptCount, quote, contacts, previews, reactions, unidentified, hasMention
|
||||
)
|
||||
}
|
||||
|
||||
@ -1613,5 +1619,6 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
|
||||
const val CREATE_MESSAGE_REQUEST_RESPONSE_COMMAND = "ALTER TABLE $TABLE_NAME ADD COLUMN $MESSAGE_REQUEST_RESPONSE INTEGER DEFAULT 0;"
|
||||
const val CREATE_REACTIONS_UNREAD_COMMAND = "ALTER TABLE $TABLE_NAME ADD COLUMN $REACTIONS_UNREAD INTEGER DEFAULT 0;"
|
||||
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;"
|
||||
}
|
||||
}
|
@ -24,6 +24,8 @@ public interface MmsSmsColumns {
|
||||
public static final String REACTIONS_UNREAD = "reactions_unread";
|
||||
public static final String REACTIONS_LAST_SEEN = "reactions_last_seen";
|
||||
|
||||
public static final String HAS_MENTION = "has_mention";
|
||||
|
||||
public static class Types {
|
||||
protected static final long TOTAL_MASK = 0xFFFFFFFF;
|
||||
|
||||
|
@ -75,7 +75,9 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsDatabase.QUOTE_ATTACHMENT,
|
||||
MmsDatabase.SHARED_CONTACTS,
|
||||
MmsDatabase.LINK_PREVIEWS,
|
||||
ReactionDatabase.REACTION_JSON_ALIAS};
|
||||
ReactionDatabase.REACTION_JSON_ALIAS,
|
||||
MmsSmsColumns.HAS_MENTION
|
||||
};
|
||||
|
||||
public MmsSmsDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
@ -186,7 +188,7 @@ public class MmsSmsDatabase extends Database {
|
||||
}
|
||||
|
||||
public Cursor getConversationSnippet(long threadId) {
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC";
|
||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
||||
|
||||
return queryTables(PROJECTION, selection, order, null);
|
||||
@ -203,7 +205,7 @@ public class MmsSmsDatabase extends Database {
|
||||
}
|
||||
|
||||
public Cursor getUnread() {
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " ASC";
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " ASC";
|
||||
String selection = "(" + MmsSmsColumns.READ + " = 0 OR " + MmsSmsColumns.REACTIONS_UNREAD + " = 1) AND " + MmsSmsColumns.NOTIFIED + " = 0";
|
||||
|
||||
return queryTables(PROJECTION, selection, order, null);
|
||||
@ -238,7 +240,7 @@ public class MmsSmsDatabase extends Database {
|
||||
}
|
||||
|
||||
public int getQuotedMessagePosition(long threadId, long quoteId, @NonNull Address address) {
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_RECEIVED + " DESC";
|
||||
String order = MmsSmsColumns.NORMALIZED_DATE_SENT + " DESC";
|
||||
String selection = MmsSmsColumns.THREAD_ID + " = " + threadId;
|
||||
|
||||
try (Cursor cursor = queryTables(new String[]{ MmsSmsColumns.NORMALIZED_DATE_SENT, MmsSmsColumns.ADDRESS }, selection, order, null)) {
|
||||
@ -337,7 +339,9 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsDatabase.QUOTE_MISSING,
|
||||
MmsDatabase.QUOTE_ATTACHMENT,
|
||||
MmsDatabase.SHARED_CONTACTS,
|
||||
MmsDatabase.LINK_PREVIEWS};
|
||||
MmsDatabase.LINK_PREVIEWS,
|
||||
MmsSmsColumns.HAS_MENTION
|
||||
};
|
||||
|
||||
String[] smsProjection = {SmsDatabase.DATE_SENT + " AS " + MmsSmsColumns.NORMALIZED_DATE_SENT,
|
||||
SmsDatabase.DATE_RECEIVED + " AS " + MmsSmsColumns.NORMALIZED_DATE_RECEIVED,
|
||||
@ -364,7 +368,9 @@ public class MmsSmsDatabase extends Database {
|
||||
MmsDatabase.QUOTE_MISSING,
|
||||
MmsDatabase.QUOTE_ATTACHMENT,
|
||||
MmsDatabase.SHARED_CONTACTS,
|
||||
MmsDatabase.LINK_PREVIEWS};
|
||||
MmsDatabase.LINK_PREVIEWS,
|
||||
MmsSmsColumns.HAS_MENTION
|
||||
};
|
||||
|
||||
SQLiteQueryBuilder mmsQueryBuilder = new SQLiteQueryBuilder();
|
||||
SQLiteQueryBuilder smsQueryBuilder = new SQLiteQueryBuilder();
|
||||
@ -408,6 +414,7 @@ public class MmsSmsDatabase extends Database {
|
||||
mmsColumnsPresent.add(MmsDatabase.STATUS);
|
||||
mmsColumnsPresent.add(MmsDatabase.UNIDENTIFIED);
|
||||
mmsColumnsPresent.add(MmsDatabase.NETWORK_FAILURE);
|
||||
mmsColumnsPresent.add(MmsSmsColumns.HAS_MENTION);
|
||||
|
||||
mmsColumnsPresent.add(AttachmentDatabase.ROW_ID);
|
||||
mmsColumnsPresent.add(AttachmentDatabase.UNIQUE_ID);
|
||||
@ -470,6 +477,7 @@ public class MmsSmsDatabase extends Database {
|
||||
smsColumnsPresent.add(SmsDatabase.DATE_RECEIVED);
|
||||
smsColumnsPresent.add(SmsDatabase.STATUS);
|
||||
smsColumnsPresent.add(SmsDatabase.UNIDENTIFIED);
|
||||
smsColumnsPresent.add(MmsSmsColumns.HAS_MENTION);
|
||||
smsColumnsPresent.add(ReactionDatabase.ROW_ID);
|
||||
smsColumnsPresent.add(ReactionDatabase.MESSAGE_ID);
|
||||
smsColumnsPresent.add(ReactionDatabase.IS_MMS);
|
||||
|
@ -123,6 +123,9 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
public static String CREATE_REACTIONS_UNREAD_COMMAND = "ALTER TABLE "+ TABLE_NAME + " " +
|
||||
"ADD COLUMN " + REACTIONS_UNREAD + " INTEGER DEFAULT 0;";
|
||||
|
||||
public static String CREATE_HAS_MENTION_COMMAND = "ALTER TABLE "+ TABLE_NAME + " " +
|
||||
"ADD COLUMN " + HAS_MENTION + " INTEGER DEFAULT 0;";
|
||||
|
||||
private static final EarlyReceiptCache earlyDeliveryReceiptCache = new EarlyReceiptCache();
|
||||
private static final EarlyReceiptCache earlyReadReceiptCache = new EarlyReceiptCache();
|
||||
|
||||
@ -208,14 +211,17 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void markAsDeleted(long messageId, boolean read) {
|
||||
public void markAsDeleted(long messageId, boolean read, boolean hasMention) {
|
||||
SQLiteDatabase database = databaseHelper.getWritableDatabase();
|
||||
ContentValues contentValues = new ContentValues();
|
||||
contentValues.put(READ, 1);
|
||||
contentValues.put(BODY, "");
|
||||
contentValues.put(HAS_MENTION, 0);
|
||||
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); }
|
||||
if (!read) {
|
||||
DatabaseComponent.get(context).threadDatabase().decrementUnread(threadId, 1, (hasMention ? 1 : 0));
|
||||
}
|
||||
updateTypeBitmask(messageId, Types.BASE_TYPE_MASK, Types.BASE_DELETED_TYPE);
|
||||
}
|
||||
|
||||
@ -446,6 +452,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
values.put(SUBSCRIPTION_ID, message.getSubscriptionId());
|
||||
values.put(EXPIRES_IN, message.getExpiresIn());
|
||||
values.put(UNIDENTIFIED, message.isUnidentified());
|
||||
values.put(HAS_MENTION, message.hasMention());
|
||||
|
||||
if (!TextUtils.isEmpty(message.getPseudoSubject()))
|
||||
values.put(SUBJECT, message.getPseudoSubject());
|
||||
@ -464,7 +471,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
long messageId = db.insert(TABLE_NAME, null, values);
|
||||
|
||||
if (unread && runIncrement) {
|
||||
DatabaseComponent.get(context).threadDatabase().incrementUnread(threadId, 1);
|
||||
DatabaseComponent.get(context).threadDatabase().incrementUnread(threadId, 1, (message.hasMention() ? 1 : 0));
|
||||
}
|
||||
|
||||
if (runThreadUpdate) {
|
||||
@ -762,7 +769,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
0, message.isSecureMessage() ? MmsSmsColumns.Types.getOutgoingEncryptedMessageType() : MmsSmsColumns.Types.getOutgoingSmsMessageType(),
|
||||
threadId, 0, new LinkedList<IdentityKeyMismatch>(),
|
||||
message.getExpiresIn(),
|
||||
System.currentTimeMillis(), 0, false, Collections.emptyList());
|
||||
System.currentTimeMillis(), 0, false, Collections.emptyList(), false);
|
||||
}
|
||||
}
|
||||
|
||||
@ -803,6 +810,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
long expireStarted = cursor.getLong(cursor.getColumnIndexOrThrow(SmsDatabase.EXPIRE_STARTED));
|
||||
String body = cursor.getString(cursor.getColumnIndexOrThrow(SmsDatabase.BODY));
|
||||
boolean unidentified = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.UNIDENTIFIED)) == 1;
|
||||
boolean hasMention = cursor.getInt(cursor.getColumnIndexOrThrow(SmsDatabase.HAS_MENTION)) == 1;
|
||||
|
||||
if (!TextSecurePreferences.isReadReceiptsEnabled(context)) {
|
||||
readReceiptCount = 0;
|
||||
@ -816,7 +824,7 @@ public class SmsDatabase extends MessagingDatabase {
|
||||
recipient,
|
||||
dateSent, dateReceived, deliveryReceiptCount, type,
|
||||
threadId, status, mismatches,
|
||||
expiresIn, expireStarted, readReceiptCount, unidentified, reactions);
|
||||
expiresIn, expireStarted, readReceiptCount, unidentified, reactions, hasMention);
|
||||
}
|
||||
|
||||
private List<IdentityKeyMismatch> getMismatches(String document) {
|
||||
|
@ -104,9 +104,9 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
threadDb.setRead(threadId, updateLastSeen)
|
||||
}
|
||||
|
||||
override fun incrementUnread(threadId: Long, amount: Int) {
|
||||
override fun incrementUnread(threadId: Long, amount: Int, unreadMentionAmount: Int) {
|
||||
val threadDb = DatabaseComponent.get(context).threadDatabase()
|
||||
threadDb.incrementUnread(threadId, amount)
|
||||
threadDb.incrementUnread(threadId, amount, unreadMentionAmount)
|
||||
}
|
||||
|
||||
override fun updateThread(threadId: Long, unarchive: Boolean) {
|
||||
@ -465,7 +465,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
|
||||
override fun insertIncomingInfoMessage(context: Context, senderPublicKey: String, groupID: String, type: SignalServiceGroup.Type, name: String, members: Collection<String>, admins: Collection<String>, sentTimestamp: Long) {
|
||||
val group = SignalServiceGroup(type, GroupUtil.getDecodedGroupIDAsData(groupID), SignalServiceGroup.GroupType.SIGNAL, name, members.toList(), null, admins.toList())
|
||||
val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true)
|
||||
val m = IncomingTextMessage(fromSerialized(senderPublicKey), 1, sentTimestamp, "", Optional.of(group), 0, true, false)
|
||||
val updateData = UpdateMessageData.buildGroupUpdate(type, name, members)?.toJSON()
|
||||
val infoMessage = IncomingGroupMessage(m, groupID, updateData, true)
|
||||
val smsDB = DatabaseComponent.get(context).smsDatabase()
|
||||
@ -728,6 +728,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
Optional.absent(),
|
||||
Optional.absent(),
|
||||
Optional.absent(),
|
||||
@ -821,6 +822,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
|
||||
false,
|
||||
false,
|
||||
true,
|
||||
false,
|
||||
Optional.absent(),
|
||||
Optional.absent(),
|
||||
Optional.absent(),
|
||||
|
@ -87,6 +87,7 @@ public class ThreadDatabase extends Database {
|
||||
private static final String SNIPPET_CHARSET = "snippet_cs";
|
||||
public static final String READ = "read";
|
||||
public static final String UNREAD_COUNT = "unread_count";
|
||||
public static final String UNREAD_MENTION_COUNT = "unread_mention_count";
|
||||
public static final String TYPE = "type";
|
||||
private static final String ERROR = "error";
|
||||
public static final String SNIPPET_TYPE = "snippet_type";
|
||||
@ -117,7 +118,7 @@ public class ThreadDatabase extends Database {
|
||||
};
|
||||
|
||||
private static final String[] THREAD_PROJECTION = {
|
||||
ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, TYPE, ERROR, SNIPPET_TYPE,
|
||||
ID, DATE, MESSAGE_COUNT, ADDRESS, SNIPPET, SNIPPET_CHARSET, READ, UNREAD_COUNT, UNREAD_MENTION_COUNT, TYPE, ERROR, SNIPPET_TYPE,
|
||||
SNIPPET_URI, ARCHIVED, STATUS, DELIVERY_RECEIPT_COUNT, EXPIRES_IN, LAST_SEEN, READ_RECEIPT_COUNT, IS_PINNED
|
||||
};
|
||||
|
||||
@ -135,6 +136,11 @@ public class ThreadDatabase extends Database {
|
||||
"ADD COLUMN " + IS_PINNED + " INTEGER DEFAULT 0;";
|
||||
}
|
||||
|
||||
public static String getUnreadMentionCountCommand() {
|
||||
return "ALTER TABLE "+ TABLE_NAME + " " +
|
||||
"ADD COLUMN " + UNREAD_MENTION_COUNT + " INTEGER DEFAULT 0;";
|
||||
}
|
||||
|
||||
public ThreadDatabase(Context context, SQLCipherOpenHelper databaseHelper) {
|
||||
super(context, databaseHelper);
|
||||
}
|
||||
@ -293,6 +299,7 @@ public class ThreadDatabase extends Database {
|
||||
ContentValues contentValues = new ContentValues(1);
|
||||
contentValues.put(READ, 1);
|
||||
contentValues.put(UNREAD_COUNT, 0);
|
||||
contentValues.put(UNREAD_MENTION_COUNT, 0);
|
||||
|
||||
if (lastSeen) {
|
||||
contentValues.put(LAST_SEEN, System.currentTimeMillis());
|
||||
@ -312,20 +319,28 @@ public class ThreadDatabase extends Database {
|
||||
}};
|
||||
}
|
||||
|
||||
public void incrementUnread(long threadId, int amount) {
|
||||
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 + " + ? WHERE " + ID + " = ?",
|
||||
new String[] {String.valueOf(amount),
|
||||
String.valueOf(threadId)});
|
||||
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) {
|
||||
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 + " - ? WHERE " + ID + " = ? AND " + UNREAD_COUNT + " > 0",
|
||||
new String[] {String.valueOf(amount),
|
||||
String.valueOf(threadId)});
|
||||
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) {
|
||||
@ -911,6 +926,7 @@ public class ThreadDatabase extends Database {
|
||||
long date = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.DATE));
|
||||
long count = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.MESSAGE_COUNT));
|
||||
int unreadCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_COUNT));
|
||||
int unreadMentionCount = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.UNREAD_MENTION_COUNT));
|
||||
long type = cursor.getLong(cursor.getColumnIndexOrThrow(ThreadDatabase.SNIPPET_TYPE));
|
||||
boolean archived = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.ARCHIVED)) != 0;
|
||||
int status = cursor.getInt(cursor.getColumnIndexOrThrow(ThreadDatabase.STATUS));
|
||||
@ -926,7 +942,7 @@ public class ThreadDatabase extends Database {
|
||||
}
|
||||
|
||||
return new ThreadRecord(body, snippetUri, recipient, date, count,
|
||||
unreadCount, threadId, deliveryReceiptCount, status, type,
|
||||
unreadCount, unreadMentionCount, threadId, deliveryReceiptCount, status, type,
|
||||
distributionType, archived, expiresIn, lastSeen, readReceiptCount, pinned);
|
||||
}
|
||||
|
||||
|
@ -85,9 +85,10 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
private static final int lokiV37 = 58;
|
||||
private static final int lokiV38 = 59;
|
||||
private static final int lokiV39 = 60;
|
||||
private static final int lokiV40 = 61;
|
||||
|
||||
// Loki - onUpgrade(...) must be updated to use Loki version numbers if Signal makes any database changes
|
||||
private static final int DATABASE_VERSION = lokiV39;
|
||||
private static final int DATABASE_VERSION = lokiV40;
|
||||
private static final int MIN_DATABASE_VERSION = lokiV7;
|
||||
private static final String CIPHER3_DATABASE_NAME = "signal.db";
|
||||
public static final String DATABASE_NAME = "signal_v4.db";
|
||||
@ -306,6 +307,9 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
db.execSQL(LokiAPIDatabase.RESET_SEQ_NO); // probably not needed but consistent with all migrations
|
||||
db.execSQL(EmojiSearchDatabase.CREATE_EMOJI_SEARCH_TABLE_COMMAND);
|
||||
db.execSQL(ReactionDatabase.CREATE_REACTION_TABLE_COMMAND);
|
||||
db.execSQL(ThreadDatabase.getUnreadMentionCountCommand());
|
||||
db.execSQL(SmsDatabase.CREATE_HAS_MENTION_COMMAND);
|
||||
db.execSQL(MmsDatabase.CREATE_HAS_MENTION_COMMAND);
|
||||
|
||||
executeStatements(db, SmsDatabase.CREATE_INDEXS);
|
||||
executeStatements(db, MmsDatabase.CREATE_INDEXS);
|
||||
@ -543,6 +547,12 @@ public class SQLCipherOpenHelper extends SQLiteOpenHelper {
|
||||
executeStatements(db, ReactionDatabase.CREATE_INDEXS);
|
||||
}
|
||||
|
||||
if (oldVersion < lokiV40) {
|
||||
db.execSQL(ThreadDatabase.getUnreadMentionCountCommand());
|
||||
db.execSQL(SmsDatabase.CREATE_HAS_MENTION_COMMAND);
|
||||
db.execSQL(MmsDatabase.CREATE_HAS_MENTION_COMMAND);
|
||||
}
|
||||
|
||||
db.setTransactionSuccessful();
|
||||
} finally {
|
||||
db.endTransaction();
|
||||
|
@ -57,12 +57,12 @@ public class MediaMmsMessageRecord extends MmsMessageRecord {
|
||||
long expiresIn, long expireStarted, int readReceiptCount,
|
||||
@Nullable Quote quote, @NonNull List<Contact> contacts,
|
||||
@NonNull List<LinkPreview> linkPreviews,
|
||||
@NonNull List<ReactionRecord> reactions, boolean unidentified)
|
||||
@NonNull List<ReactionRecord> reactions, boolean unidentified, boolean hasMention)
|
||||
{
|
||||
super(id, body, conversationRecipient, individualRecipient, dateSent,
|
||||
dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox, mismatches, failures,
|
||||
expiresIn, expireStarted, slideDeck, readReceiptCount, quote, contacts,
|
||||
linkPreviews, unidentified, reactions);
|
||||
linkPreviews, unidentified, reactions, hasMention);
|
||||
this.partCount = partCount;
|
||||
}
|
||||
|
||||
|
@ -51,7 +51,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
private final long expireStarted;
|
||||
private final boolean unidentified;
|
||||
public final long id;
|
||||
private final List<ReactionRecord> reactions;
|
||||
private final List<ReactionRecord> reactions;
|
||||
private final boolean hasMention;
|
||||
|
||||
public abstract boolean isMms();
|
||||
public abstract boolean isMmsNotification();
|
||||
@ -63,7 +64,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
List<IdentityKeyMismatch> mismatches,
|
||||
List<NetworkFailure> networkFailures,
|
||||
long expiresIn, long expireStarted,
|
||||
int readReceiptCount, boolean unidentified, List<ReactionRecord> reactions)
|
||||
int readReceiptCount, boolean unidentified, List<ReactionRecord> reactions, boolean hasMention)
|
||||
{
|
||||
super(body, conversationRecipient, dateSent, dateReceived,
|
||||
threadId, deliveryStatus, deliveryReceiptCount, type, readReceiptCount);
|
||||
@ -75,6 +76,7 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
this.expireStarted = expireStarted;
|
||||
this.unidentified = unidentified;
|
||||
this.reactions = reactions;
|
||||
this.hasMention = hasMention;
|
||||
}
|
||||
|
||||
public long getId() {
|
||||
@ -97,6 +99,8 @@ public abstract class MessageRecord extends DisplayRecord {
|
||||
}
|
||||
public long getExpireStarted() { return expireStarted; }
|
||||
|
||||
public boolean getHasMention() { return hasMention; }
|
||||
|
||||
public boolean isMediaPending() {
|
||||
return false;
|
||||
}
|
||||
|
@ -27,9 +27,9 @@ public abstract class MmsMessageRecord extends MessageRecord {
|
||||
List<NetworkFailure> networkFailures, long expiresIn,
|
||||
long expireStarted, @NonNull SlideDeck slideDeck, int readReceiptCount,
|
||||
@Nullable Quote quote, @NonNull List<Contact> contacts,
|
||||
@NonNull List<LinkPreview> linkPreviews, boolean unidentified, List<ReactionRecord> reactions)
|
||||
@NonNull List<LinkPreview> linkPreviews, boolean unidentified, List<ReactionRecord> reactions, boolean hasMention)
|
||||
{
|
||||
super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, expiresIn, expireStarted, readReceiptCount, unidentified, reactions);
|
||||
super(id, body, conversationRecipient, individualRecipient, dateSent, dateReceived, threadId, deliveryStatus, deliveryReceiptCount, type, mismatches, networkFailures, expiresIn, expireStarted, readReceiptCount, unidentified, reactions, hasMention);
|
||||
this.slideDeck = slideDeck;
|
||||
this.quote = quote;
|
||||
this.contacts.addAll(contacts);
|
||||
|
@ -50,12 +50,12 @@ public class NotificationMmsMessageRecord extends MmsMessageRecord {
|
||||
long dateSent, long dateReceived, int deliveryReceiptCount,
|
||||
long threadId, byte[] contentLocation, long messageSize,
|
||||
long expiry, int status, byte[] transactionId, long mailbox,
|
||||
SlideDeck slideDeck, int readReceiptCount)
|
||||
SlideDeck slideDeck, int readReceiptCount, boolean hasMention)
|
||||
{
|
||||
super(id, "", conversationRecipient, individualRecipient,
|
||||
dateSent, dateReceived, threadId, Status.STATUS_NONE, deliveryReceiptCount, mailbox,
|
||||
emptyList(), emptyList(),
|
||||
0, 0, slideDeck, readReceiptCount, null, emptyList(), emptyList(), false, emptyList());
|
||||
0, 0, slideDeck, readReceiptCount, null, emptyList(), emptyList(), false, emptyList(), hasMention);
|
||||
|
||||
this.contentLocation = contentLocation;
|
||||
this.messageSize = messageSize;
|
||||
|
@ -43,12 +43,12 @@ public class SmsMessageRecord extends MessageRecord {
|
||||
long type, long threadId,
|
||||
int status, List<IdentityKeyMismatch> mismatches,
|
||||
long expiresIn, long expireStarted,
|
||||
int readReceiptCount, boolean unidentified, List<ReactionRecord> reactions)
|
||||
int readReceiptCount, boolean unidentified, List<ReactionRecord> reactions, boolean hasMention)
|
||||
{
|
||||
super(id, body, recipient, individualRecipient,
|
||||
dateSent, dateReceived, threadId, status, deliveryReceiptCount, type,
|
||||
mismatches, new LinkedList<>(),
|
||||
expiresIn, expireStarted, readReceiptCount, unidentified, reactions);
|
||||
expiresIn, expireStarted, readReceiptCount, unidentified, reactions, hasMention);
|
||||
}
|
||||
|
||||
public long getType() {
|
||||
|
@ -45,6 +45,7 @@ public class ThreadRecord extends DisplayRecord {
|
||||
private @Nullable final Uri snippetUri;
|
||||
private final long count;
|
||||
private final int unreadCount;
|
||||
private final int unreadMentionCount;
|
||||
private final int distributionType;
|
||||
private final boolean archived;
|
||||
private final long expiresIn;
|
||||
@ -53,19 +54,20 @@ public class ThreadRecord extends DisplayRecord {
|
||||
|
||||
public ThreadRecord(@NonNull String body, @Nullable Uri snippetUri,
|
||||
@NonNull Recipient recipient, long date, long count, int unreadCount,
|
||||
long threadId, int deliveryReceiptCount, int status, long snippetType,
|
||||
int distributionType, boolean archived, long expiresIn, long lastSeen,
|
||||
int readReceiptCount, boolean pinned)
|
||||
int unreadMentionCount, long threadId, int deliveryReceiptCount, int status,
|
||||
long snippetType, int distributionType, boolean archived, long expiresIn,
|
||||
long lastSeen, int readReceiptCount, boolean pinned)
|
||||
{
|
||||
super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount);
|
||||
this.snippetUri = snippetUri;
|
||||
this.count = count;
|
||||
this.unreadCount = unreadCount;
|
||||
this.distributionType = distributionType;
|
||||
this.archived = archived;
|
||||
this.expiresIn = expiresIn;
|
||||
this.lastSeen = lastSeen;
|
||||
this.pinned = pinned;
|
||||
this.snippetUri = snippetUri;
|
||||
this.count = count;
|
||||
this.unreadCount = unreadCount;
|
||||
this.unreadMentionCount = unreadMentionCount;
|
||||
this.distributionType = distributionType;
|
||||
this.archived = archived;
|
||||
this.expiresIn = expiresIn;
|
||||
this.lastSeen = lastSeen;
|
||||
this.pinned = pinned;
|
||||
}
|
||||
|
||||
public @Nullable Uri getSnippetUri() {
|
||||
@ -147,6 +149,10 @@ public class ThreadRecord extends DisplayRecord {
|
||||
return unreadCount;
|
||||
}
|
||||
|
||||
public int getUnreadMentionCount() {
|
||||
return unreadMentionCount;
|
||||
}
|
||||
|
||||
public long getDate() {
|
||||
return getDateReceived();
|
||||
}
|
||||
|
@ -25,17 +25,17 @@ import org.thoughtcrime.securesms.util.getAccentColor
|
||||
import java.util.Locale
|
||||
|
||||
class ConversationView : LinearLayout {
|
||||
private lateinit var binding: ViewConversationBinding
|
||||
private val binding: ViewConversationBinding by lazy { ViewConversationBinding.bind(this) }
|
||||
private val screenWidth = Resources.getSystem().displayMetrics.widthPixels
|
||||
var thread: ThreadRecord? = null
|
||||
|
||||
// region Lifecycle
|
||||
constructor(context: Context) : super(context) { initialize() }
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() }
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr) { initialize() }
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet, defStyleAttr: Int) : super(context, attrs, defStyleAttr)
|
||||
|
||||
private fun initialize() {
|
||||
binding = ViewConversationBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
override fun onFinishInflate() {
|
||||
super.onFinishInflate()
|
||||
layoutParams = RecyclerView.LayoutParams(screenWidth, RecyclerView.LayoutParams.WRAP_CONTENT)
|
||||
}
|
||||
// endregion
|
||||
@ -53,7 +53,7 @@ class ConversationView : LinearLayout {
|
||||
} else {
|
||||
binding.conversationViewDisplayNameTextView.setCompoundDrawablesRelativeWithIntrinsicBounds(0, 0, 0, 0)
|
||||
}
|
||||
background = if (thread.unreadCount > 0) {
|
||||
binding.root.background = if (thread.unreadCount > 0) {
|
||||
ContextCompat.getDrawable(context, R.drawable.conversation_unread_background)
|
||||
} else {
|
||||
ContextCompat.getDrawable(context, R.drawable.conversation_view_background)
|
||||
@ -79,8 +79,9 @@ class ConversationView : LinearLayout {
|
||||
binding.unreadCountTextView.text = formattedUnreadCount
|
||||
val textSize = if (unreadCount < 1000) 12.0f else 10.0f
|
||||
binding.unreadCountTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
|
||||
binding.unreadCountIndicator.background.setTint(context.getAccentColor())
|
||||
binding.unreadCountIndicator.isVisible = (unreadCount != 0 && !thread.isRead)
|
||||
binding.unreadMentionTextView.setTextSize(TypedValue.COMPLEX_UNIT_DIP, textSize)
|
||||
binding.unreadMentionIndicator.isVisible = (thread.unreadMentionCount != 0 && thread.recipient.address.isGroup)
|
||||
val senderDisplayName = getUserDisplayName(thread.recipient)
|
||||
?: thread.recipient.address.toString()
|
||||
binding.conversationViewDisplayNameTextView.text = senderDisplayName
|
||||
|
@ -1,12 +1,14 @@
|
||||
package org.thoughtcrime.securesms.home
|
||||
|
||||
import android.content.Context
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListUpdateCallback
|
||||
import androidx.recyclerview.widget.RecyclerView
|
||||
import androidx.recyclerview.widget.RecyclerView.NO_ID
|
||||
import network.loki.messenger.R
|
||||
import org.thoughtcrime.securesms.database.model.ThreadRecord
|
||||
import org.thoughtcrime.securesms.mms.GlideRequests
|
||||
|
||||
@ -76,19 +78,20 @@ class HomeAdapter(
|
||||
HeaderFooterViewHolder(header!!)
|
||||
}
|
||||
ITEM -> {
|
||||
val view = ConversationView(context)
|
||||
view.setOnClickListener { view.thread?.let { listener.onConversationClick(it) } }
|
||||
view.setOnLongClickListener {
|
||||
view.thread?.let { listener.onLongConversationClick(it) }
|
||||
val conversationView = LayoutInflater.from(parent.context).inflate(R.layout.view_conversation, parent, false) as ConversationView
|
||||
val viewHolder = ConversationViewHolder(conversationView)
|
||||
viewHolder.view.setOnClickListener { viewHolder.view.thread?.let { listener.onConversationClick(it) } }
|
||||
viewHolder.view.setOnLongClickListener {
|
||||
viewHolder.view.thread?.let { listener.onLongConversationClick(it) }
|
||||
true
|
||||
}
|
||||
ViewHolder(view)
|
||||
viewHolder
|
||||
}
|
||||
else -> throw Exception("viewType $viewType isn't valid")
|
||||
}
|
||||
|
||||
override fun onBindViewHolder(holder: RecyclerView.ViewHolder, position: Int) {
|
||||
if (holder is ViewHolder) {
|
||||
if (holder is ConversationViewHolder) {
|
||||
val offset = if (hasHeaderView()) position - 1 else position
|
||||
val thread = data[offset]
|
||||
val isTyping = typingThreadIDs.contains(thread.threadId)
|
||||
@ -97,7 +100,7 @@ class HomeAdapter(
|
||||
}
|
||||
|
||||
override fun onViewRecycled(holder: RecyclerView.ViewHolder) {
|
||||
if (holder is ViewHolder) {
|
||||
if (holder is ConversationViewHolder) {
|
||||
holder.view.recycle()
|
||||
} else {
|
||||
super.onViewRecycled(holder)
|
||||
@ -110,7 +113,7 @@ class HomeAdapter(
|
||||
|
||||
override fun getItemCount(): Int = data.size + if (hasHeaderView()) 1 else 0
|
||||
|
||||
class ViewHolder(val view: ConversationView) : RecyclerView.ViewHolder(view)
|
||||
class ConversationViewHolder(val view: ConversationView) : RecyclerView.ViewHolder(view)
|
||||
|
||||
class HeaderFooterViewHolder(itemView: View) : RecyclerView.ViewHolder(itemView)
|
||||
|
||||
|
@ -111,6 +111,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
|
||||
duration * 1000L, true,
|
||||
false,
|
||||
false,
|
||||
false,
|
||||
Optional.absent(),
|
||||
groupInfo,
|
||||
Optional.absent(),
|
||||
|
@ -135,7 +135,8 @@ object MockDataGenerator {
|
||||
Optional.absent(),
|
||||
0,
|
||||
false,
|
||||
-1
|
||||
-1,
|
||||
false
|
||||
),
|
||||
(timestampNow - (index * 5000)),
|
||||
false,
|
||||
@ -264,7 +265,8 @@ object MockDataGenerator {
|
||||
Optional.absent(),
|
||||
0,
|
||||
false,
|
||||
-1
|
||||
-1,
|
||||
false
|
||||
),
|
||||
(timestampNow - (index * 5000)),
|
||||
false,
|
||||
@ -389,7 +391,8 @@ object MockDataGenerator {
|
||||
Optional.absent(),
|
||||
0,
|
||||
false,
|
||||
-1
|
||||
-1,
|
||||
false
|
||||
),
|
||||
(timestampNow - (index * 5000)),
|
||||
false,
|
||||
|
@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<LinearLayout
|
||||
<org.thoughtcrime.securesms.home.ConversationView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
@ -66,7 +66,7 @@
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="4dp"
|
||||
app:layout_constraintStart_toEndOf="@id/conversationViewDisplayNameTextView"
|
||||
app:layout_constraintEnd_toStartOf="@id/timestampTextView"
|
||||
app:layout_constraintEnd_toStartOf="@id/unreadMentionIndicator"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:minWidth="20dp"
|
||||
@ -89,6 +89,36 @@
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/unreadMentionIndicator"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="20dp"
|
||||
android:layout_marginStart="4dp"
|
||||
app:layout_constraintStart_toEndOf="@id/unreadCountIndicator"
|
||||
app:layout_constraintEnd_toStartOf="@id/timestampTextView"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
android:minWidth="20dp"
|
||||
android:maxWidth="40dp"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:background="@drawable/rounded_rectangle"
|
||||
android:backgroundTint="?unreadIndicatorBackgroundColor">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/unreadMentionTextView"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerInParent="true"
|
||||
android:paddingBottom="3dp"
|
||||
android:textColor="?unreadIndicatorTextColor"
|
||||
android:textSize="@dimen/very_small_font_size"
|
||||
android:textStyle="bold"
|
||||
android:text="@"
|
||||
tools:textColor="?android:textColorPrimary" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/timestampTextView"
|
||||
android:layout_width="wrap_content"
|
||||
@ -157,4 +187,4 @@
|
||||
|
||||
</FrameLayout>
|
||||
|
||||
</LinearLayout>
|
||||
</org.thoughtcrime.securesms.home.ConversationView>
|
||||
|
@ -141,22 +141,25 @@
|
||||
|
||||
<color name="ocean_accent">#57C9FA</color>
|
||||
|
||||
<color name="ocean_dark_0">#111111</color>
|
||||
<color name="ocean_dark_0">#000000</color>
|
||||
<color name="ocean_dark_1">#1A1C28</color>
|
||||
<color name="ocean_dark_2">#252735</color>
|
||||
<color name="ocean_dark_3">#2B2D40</color>
|
||||
<color name="ocean_dark_4">#3D4A5D</color>
|
||||
<color name="ocean_dark_5">#A6A9CE</color>
|
||||
<color name="ocean_dark_6">#FFFFFF</color>
|
||||
<color name="ocean_dark_6">#5CAACC</color>
|
||||
<color name="ocean_dark_7">#FFFFFF</color>
|
||||
|
||||
<color name="ocean_light_0">#19345D</color>
|
||||
<color name="ocean_light_1">#6A6E90</color>
|
||||
<color name="ocean_light_2">#5CAACC</color>
|
||||
<color name="ocean_light_3">#B3EDF2</color>
|
||||
<color name="ocean_light_4">#E7F3F4</color>
|
||||
<color name="ocean_light_5">#ECFAFB</color>
|
||||
<color name="ocean_light_6">#FCFFFF</color>
|
||||
<color name="ocean_light_0">#000000</color>
|
||||
<color name="ocean_light_1">#19345D</color>
|
||||
<color name="ocean_light_2">#6A6E90</color>
|
||||
<color name="ocean_light_3">#5CAACC</color>
|
||||
<color name="ocean_light_4">#B3EDF2</color>
|
||||
<color name="ocean_light_5">#E7F3F4</color>
|
||||
<color name="ocean_light_6">#ECFAFB</color>
|
||||
<color name="ocean_light_7">#FCFFFF</color>
|
||||
|
||||
<color name="danger">#EA5545</color>
|
||||
<color name="danger_dark">#FF3A3A</color>
|
||||
<color name="danger_light">#E12D19</color>
|
||||
|
||||
</resources>
|
||||
|
@ -277,7 +277,7 @@
|
||||
<item name="android:textColorSecondary">?android:textColorPrimary</item>
|
||||
</style>
|
||||
<style name="Ocean.Light.BottomSheet" parent="Theme.MaterialComponents.BottomSheetDialog">
|
||||
<item name="colorPrimary">@color/ocean_light_5</item>
|
||||
<item name="colorPrimary">@color/ocean_light_6</item>
|
||||
<item name="bottomSheetStyle">@style/Widget.Session.BottomSheetDialog</item>
|
||||
<item name="dialog_border">@color/transparent_black_15</item>
|
||||
<item name="android:textColorPrimary">@color/black</item>
|
||||
@ -354,7 +354,7 @@
|
||||
<item name="conversation_unread_background_color">@color/classic_dark_2</item>
|
||||
<item name="conversation_pinned_icon_color">?android:textColorSecondary</item>
|
||||
<item name="unreadIndicatorBackgroundColor">@color/classic_dark_3</item>
|
||||
<item name="unreadIndicatorTextColor">@color/classic_dark_0</item>
|
||||
<item name="unreadIndicatorTextColor">@color/classic_dark_6</item>
|
||||
|
||||
<!-- New conversation button -->
|
||||
<item name="conversation_color_non_main">@color/classic_dark_2</item>
|
||||
@ -477,13 +477,13 @@
|
||||
|
||||
<style name="Ocean.Dark">
|
||||
<!-- Main styles -->
|
||||
<item name="sessionLogoTint">@color/ocean_dark_6</item>
|
||||
<item name="sessionLogoTint">@color/ocean_dark_7</item>
|
||||
<item name="colorPrimary">@color/ocean_dark_2</item>
|
||||
<item name="colorPrimaryDark">@color/ocean_dark_2</item>
|
||||
<item name="colorControlNormal">@color/ocean_dark_6</item>
|
||||
<item name="colorControlNormal">@color/ocean_dark_7</item>
|
||||
<item name="colorControlActivated">?colorAccent</item>
|
||||
<item name="android:colorControlHighlight">?colorAccent</item>
|
||||
<item name="android:textColorPrimary">@color/ocean_dark_6</item>
|
||||
<item name="android:textColorPrimary">@color/ocean_dark_7</item>
|
||||
<item name="android:textColorSecondary">@color/ocean_dark_5</item>
|
||||
<item name="android:textColorTertiary">@color/ocean_dark_5</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
@ -509,7 +509,7 @@
|
||||
|
||||
<!-- Home screen -->
|
||||
<item name="searchBackgroundColor">@color/ocean_dark_3</item>
|
||||
<item name="searchIconColor">@color/ocean_dark_6</item>
|
||||
<item name="searchIconColor">@color/ocean_dark_7</item>
|
||||
<item name="searchHintColor">@color/ocean_dark_5</item>
|
||||
<item name="searchTextColor">?android:textColorPrimary</item>
|
||||
<item name="searchHighlightTint">?colorAccent</item>
|
||||
@ -534,16 +534,16 @@
|
||||
|
||||
<!-- Conversation -->
|
||||
<item name="message_received_background_color">@color/ocean_dark_4</item>
|
||||
<item name="message_received_text_color">@color/ocean_dark_6</item>
|
||||
<item name="message_received_text_color">@color/ocean_dark_7</item>
|
||||
<item name="message_sent_background_color">?colorAccent</item>
|
||||
<item name="message_sent_text_color">@color/ocean_dark_0</item>
|
||||
<item name="message_status_color">@color/ocean_dark_5</item>
|
||||
<item name="input_bar_background">@color/ocean_dark_1</item>
|
||||
<item name="input_bar_text_hint">@color/ocean_dark_5</item>
|
||||
<item name="input_bar_text_user">@color/ocean_dark_6</item>
|
||||
<item name="input_bar_text_user">@color/ocean_dark_7</item>
|
||||
<item name="input_bar_border">@color/ocean_dark_4</item>
|
||||
<item name="input_bar_button_background">@color/ocean_dark_2</item>
|
||||
<item name="input_bar_button_text_color">@color/ocean_dark_6</item>
|
||||
<item name="input_bar_button_text_color">@color/ocean_dark_7</item>
|
||||
<item name="input_bar_button_background_opaque">@color/ocean_dark_4</item>
|
||||
<item name="input_bar_button_background_opaque_border">@color/ocean_dark_2</item>
|
||||
<item name="input_bar_lock_view_background">?colorPrimary</item>
|
||||
@ -559,26 +559,26 @@
|
||||
|
||||
<style name="Ocean.Light">
|
||||
<!-- Main styles -->
|
||||
<item name="sessionLogoTint">@color/ocean_light_0</item>
|
||||
<item name="colorPrimary">@color/ocean_light_6</item>
|
||||
<item name="colorPrimaryDark">@color/ocean_light_5</item>
|
||||
<item name="colorControlNormal">@color/ocean_light_0</item>
|
||||
<item name="sessionLogoTint">@color/ocean_light_1</item>
|
||||
<item name="colorPrimary">@color/ocean_light_7</item>
|
||||
<item name="colorPrimaryDark">@color/ocean_light_6</item>
|
||||
<item name="colorControlNormal">@color/ocean_light_1</item>
|
||||
<item name="colorControlActivated">?colorAccent</item>
|
||||
<item name="android:colorControlHighlight">?colorAccent</item>
|
||||
<item name="android:textColorPrimary">@color/ocean_light_0</item>
|
||||
<item name="android:textColorSecondary">@color/ocean_light_1</item>
|
||||
<item name="android:textColorTertiary">@color/ocean_light_1</item>
|
||||
<item name="android:textColorPrimary">@color/ocean_light_1</item>
|
||||
<item name="android:textColorSecondary">@color/ocean_light_2</item>
|
||||
<item name="android:textColorTertiary">@color/ocean_light_2</item>
|
||||
<item name="android:textColor">?android:textColorPrimary</item>
|
||||
<item name="android:textColorHint">@color/ocean_light_5</item>
|
||||
<item name="android:navigationBarColor">@color/ocean_light_6</item>
|
||||
<item name="android:textColorHint">@color/ocean_light_6</item>
|
||||
<item name="android:navigationBarColor">@color/ocean_light_7</item>
|
||||
<item name="android:windowBackground">?colorPrimary</item>
|
||||
<item name="android:colorBackground">@color/default_background_start</item>
|
||||
<item name="default_background_end">@color/ocean_light_6</item>
|
||||
<item name="default_background_start">@color/ocean_light_5</item>
|
||||
<item name="colorCellBackground">@color/ocean_light_4</item>
|
||||
<item name="colorSettingsBackground">@color/ocean_light_5</item>
|
||||
<item name="colorDividerBackground">@color/ocean_light_2</item>
|
||||
<item name="colorCellRipple">@color/ocean_light_3</item>
|
||||
<item name="default_background_end">@color/ocean_light_7</item>
|
||||
<item name="default_background_start">@color/ocean_light_6</item>
|
||||
<item name="colorCellBackground">@color/ocean_light_5</item>
|
||||
<item name="colorSettingsBackground">@color/ocean_light_6</item>
|
||||
<item name="colorDividerBackground">@color/ocean_light_3</item>
|
||||
<item name="colorCellRipple">@color/ocean_light_4</item>
|
||||
<item name="bottomSheetDialogTheme">@style/Ocean.Light.BottomSheet</item>
|
||||
<item name="actionBarPopupTheme">@style/Light.Popup</item>
|
||||
<item name="popupTheme">?actionBarPopupTheme</item>
|
||||
@ -587,7 +587,7 @@
|
||||
<item name="actionBarTheme">@style/ThemeOverlay.AppCompat.ActionBar</item>
|
||||
<item name="actionBarStyle">@style/Widget.Session.ActionBar</item>
|
||||
<item name="prominentButtonColor">?android:textColorPrimary</item>
|
||||
<item name="elementBorderColor">@color/ocean_light_2</item>
|
||||
<item name="elementBorderColor">@color/ocean_light_3</item>
|
||||
|
||||
<!-- Light mode -->
|
||||
<item name="theme_type">light</item>
|
||||
@ -598,49 +598,49 @@
|
||||
<item name="android:statusBarColor">?colorPrimary</item>
|
||||
|
||||
|
||||
<item name="searchBackgroundColor">@color/ocean_light_4</item>
|
||||
<item name="searchIconColor">@color/ocean_light_0</item>
|
||||
<item name="searchHintColor">@color/ocean_light_1</item>
|
||||
<item name="searchTextColor">@color/ocean_light_0</item>
|
||||
<item name="searchBackgroundColor">@color/ocean_light_5</item>
|
||||
<item name="searchIconColor">@color/ocean_light_1</item>
|
||||
<item name="searchHintColor">@color/ocean_light_2</item>
|
||||
<item name="searchTextColor">@color/ocean_light_1</item>
|
||||
<item name="searchHighlightTint">?colorAccent</item>
|
||||
|
||||
<item name="home_gradient_start">#00000000</item>
|
||||
<item name="home_gradient_end">@color/ocean_light_6</item>
|
||||
<item name="home_gradient_end">@color/ocean_light_7</item>
|
||||
<item name="conversation_shadow_non_main">@color/black</item>
|
||||
<item name="conversation_shadow_main">@color/black</item>
|
||||
<item name="conversation_menu_background_color">@color/ocean_light_6</item>
|
||||
<item name="conversation_menu_cell_color">@color/ocean_light_5</item>
|
||||
<item name="conversation_menu_border_color">@color/ocean_light_2</item>
|
||||
<item name="conversationMenuSearchBackgroundColor">@color/ocean_light_6</item>
|
||||
<item name="conversation_menu_background_color">@color/ocean_light_7</item>
|
||||
<item name="conversation_menu_cell_color">@color/ocean_light_6</item>
|
||||
<item name="conversation_menu_border_color">@color/ocean_light_3</item>
|
||||
<item name="conversationMenuSearchBackgroundColor">@color/ocean_light_7</item>
|
||||
|
||||
<item name="unreadIndicatorBackgroundColor">?colorAccent</item>
|
||||
<item name="unreadIndicatorTextColor">@color/ocean_light_0</item>
|
||||
<item name="unreadIndicatorTextColor">@color/ocean_light_1</item>
|
||||
|
||||
<!-- Conversation -->
|
||||
<item name="message_received_background_color">@color/ocean_light_3</item>
|
||||
<item name="message_received_text_color">@color/ocean_light_0</item>
|
||||
<item name="message_received_background_color">@color/ocean_light_4</item>
|
||||
<item name="message_received_text_color">@color/ocean_light_1</item>
|
||||
<item name="message_sent_background_color">?colorAccent</item>
|
||||
<item name="message_sent_text_color">@color/ocean_light_0</item>
|
||||
<item name="message_sent_text_color">@color/ocean_light_1</item>
|
||||
<item name="message_status_color">@color/ocean_light_2</item>
|
||||
<item name="input_bar_background">@color/ocean_light_6</item>
|
||||
<item name="input_bar_text_hint">@color/ocean_light_1</item>
|
||||
<item name="input_bar_text_user">@color/ocean_light_0</item>
|
||||
<item name="input_bar_border">@color/ocean_light_2</item>
|
||||
<item name="input_bar_button_background">@color/ocean_light_4</item>
|
||||
<item name="input_bar_button_background_opaque">@color/ocean_light_4</item>
|
||||
<item name="input_bar_button_text_color">@color/ocean_light_0</item>
|
||||
<item name="input_bar_button_background_opaque_border">@color/ocean_light_0</item>
|
||||
<item name="input_bar_lock_view_background">@color/ocean_light_4</item>
|
||||
<item name="input_bar_lock_view_border">@color/ocean_light_0</item>
|
||||
<item name="input_bar_background">@color/ocean_light_7</item>
|
||||
<item name="input_bar_text_hint">@color/ocean_light_2</item>
|
||||
<item name="input_bar_text_user">@color/ocean_light_1</item>
|
||||
<item name="input_bar_border">@color/ocean_light_3</item>
|
||||
<item name="input_bar_button_background">@color/ocean_light_5</item>
|
||||
<item name="input_bar_button_background_opaque">@color/ocean_light_5</item>
|
||||
<item name="input_bar_button_text_color">@color/ocean_light_1</item>
|
||||
<item name="input_bar_button_background_opaque_border">@color/ocean_light_1</item>
|
||||
<item name="input_bar_lock_view_background">@color/ocean_light_5</item>
|
||||
<item name="input_bar_lock_view_border">@color/ocean_light_1</item>
|
||||
<item name="mention_candidates_view_background">?colorCellBackground</item>
|
||||
<item name="mention_candidates_view_background_ripple">?colorCellRipple</item>
|
||||
<item name="scroll_to_bottom_button_background">?input_bar_button_background_opaque</item>
|
||||
<item name="scroll_to_bottom_button_border">?input_bar_button_background_opaque_border</item>
|
||||
<item name="conversation_unread_count_indicator_background">?colorAccent</item>
|
||||
<item name="conversation_pinned_background_color">?colorCellBackground</item>
|
||||
<item name="conversation_unread_background_color">@color/ocean_light_5</item>
|
||||
<item name="conversation_unread_background_color">@color/ocean_light_6</item>
|
||||
<item name="conversation_pinned_icon_color">?android:textColorSecondary</item>
|
||||
<item name="message_selected">@color/ocean_light_5</item>
|
||||
<item name="message_selected">@color/ocean_light_6</item>
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
|
@ -23,7 +23,7 @@ interface MessageDataProvider {
|
||||
fun getMessageIDs(serverIDs: List<Long>, threadID: Long): Pair<List<Long>, List<Long>>
|
||||
fun deleteMessage(messageID: Long, isSms: Boolean)
|
||||
fun deleteMessages(messageIDs: List<Long>, threadId: Long, isSms: Boolean)
|
||||
fun updateMessageAsDeleted(timestamp: Long, author: String)
|
||||
fun updateMessageAsDeleted(timestamp: Long, author: String): Long?
|
||||
fun getServerHashForMessage(messageID: Long): String?
|
||||
fun getDatabaseAttachment(attachmentId: Long): DatabaseAttachment?
|
||||
fun getAttachmentStream(attachmentId: Long): SessionServiceAttachmentStream?
|
||||
@ -38,7 +38,7 @@ interface MessageDataProvider {
|
||||
fun isOutgoingMessage(timestamp: Long): Boolean
|
||||
fun handleSuccessfulAttachmentUpload(attachmentId: Long, attachmentStream: SignalServiceAttachmentStream, attachmentKey: ByteArray, uploadResult: UploadResult)
|
||||
fun handleFailedAttachmentUpload(attachmentId: Long)
|
||||
fun getMessageForQuote(timestamp: Long, author: Address): Pair<Long, Boolean>?
|
||||
fun getMessageForQuote(timestamp: Long, author: Address): Triple<Long, Boolean, String>?
|
||||
fun getAttachmentsAndLinkPreviewFor(mmsId: Long): List<Attachment>
|
||||
fun getMessageBodyFor(timestamp: Long, author: String): String
|
||||
fun getAttachmentIDsFor(messageID: Long): List<Long>
|
||||
|
@ -174,7 +174,7 @@ interface StorageProtocol {
|
||||
*/
|
||||
fun persist(message: VisibleMessage, quotes: QuoteModel?, linkPreview: List<LinkPreview?>, groupPublicKey: String?, openGroupID: String?, attachments: List<Attachment>, runIncrement: Boolean, runThreadUpdate: Boolean): Long?
|
||||
fun markConversationAsRead(threadId: Long, updateLastSeen: Boolean)
|
||||
fun incrementUnread(threadId: Long, amount: Int)
|
||||
fun incrementUnread(threadId: Long, amount: Int, unreadMentionAmount: Int)
|
||||
fun updateThread(threadId: Long, unarchive: Boolean)
|
||||
fun insertDataExtractionNotificationMessage(senderPublicKey: String, message: DataExtractionNotificationInfoMessage, sentTimestamp: Long)
|
||||
fun insertMessageRequestResponse(response: MessageRequestResponse)
|
||||
|
@ -11,13 +11,11 @@ import org.session.libsession.database.StorageProtocol
|
||||
import org.session.libsession.messaging.MessagingModuleConfiguration
|
||||
import org.session.libsession.messaging.messages.Message
|
||||
import org.session.libsession.messaging.messages.control.ExpirationTimerUpdate
|
||||
import org.session.libsession.messaging.messages.control.UnsendRequest
|
||||
import org.session.libsession.messaging.messages.visible.ParsedMessage
|
||||
import org.session.libsession.messaging.messages.visible.VisibleMessage
|
||||
import org.session.libsession.messaging.open_groups.OpenGroupApi
|
||||
import org.session.libsession.messaging.sending_receiving.MessageReceiver
|
||||
import org.session.libsession.messaging.sending_receiving.handle
|
||||
import org.session.libsession.messaging.sending_receiving.handleOpenGroupReactions
|
||||
import org.session.libsession.messaging.sending_receiving.handleVisibleMessage
|
||||
import org.session.libsession.messaging.sending_receiving.*
|
||||
import org.session.libsession.messaging.utilities.Data
|
||||
import org.session.libsession.messaging.utilities.SessionId
|
||||
import org.session.libsession.messaging.utilities.SodiumUtilities
|
||||
@ -119,25 +117,42 @@ class BatchMessageReceiveJob(
|
||||
runBlocking(Dispatchers.IO) {
|
||||
val deferredThreadMap = threadMap.entries.map { (threadId, messages) ->
|
||||
async {
|
||||
val messageIds = mutableListOf<Pair<Long, Boolean>>()
|
||||
// The LinkedHashMap should preserve insertion order
|
||||
val messageIds = linkedMapOf<Long, Pair<Boolean, Boolean>>()
|
||||
|
||||
messages.forEach { (parameters, message, proto) ->
|
||||
try {
|
||||
if (message is VisibleMessage) {
|
||||
val messageId = MessageReceiver.handleVisibleMessage(message, proto, openGroupID,
|
||||
runIncrement = false,
|
||||
runThreadUpdate = false,
|
||||
runProfileUpdate = true
|
||||
)
|
||||
if (messageId != null && message.reaction == null) {
|
||||
val isUserBlindedSender = message.sender == serverPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId(
|
||||
IdPrefix.BLINDED, it.publicKey.asBytes).hexString }
|
||||
messageIds += messageId to (message.sender == localUserPublicKey || isUserBlindedSender)
|
||||
when (message) {
|
||||
is VisibleMessage -> {
|
||||
val messageId = MessageReceiver.handleVisibleMessage(message, proto, openGroupID,
|
||||
runIncrement = false,
|
||||
runThreadUpdate = false,
|
||||
runProfileUpdate = true
|
||||
)
|
||||
|
||||
if (messageId != null && message.reaction == null) {
|
||||
val isUserBlindedSender = message.sender == serverPublicKey?.let { SodiumUtilities.blindedKeyPair(it, MessagingModuleConfiguration.shared.getUserED25519KeyPair()!!) }?.let { SessionId(
|
||||
IdPrefix.BLINDED, it.publicKey.asBytes).hexString }
|
||||
messageIds[messageId] = Pair(
|
||||
(message.sender == localUserPublicKey || isUserBlindedSender),
|
||||
message.hasMention
|
||||
)
|
||||
}
|
||||
parameters.openGroupMessageServerID?.let {
|
||||
MessageReceiver.handleOpenGroupReactions(threadId, it, parameters.reactions)
|
||||
}
|
||||
}
|
||||
parameters.openGroupMessageServerID?.let {
|
||||
MessageReceiver.handleOpenGroupReactions(threadId, it, parameters.reactions)
|
||||
|
||||
is UnsendRequest -> {
|
||||
val deletedMessageId = MessageReceiver.handleUnsendRequest(message)
|
||||
|
||||
// If we removed a message then ensure it isn't in the 'messageIds'
|
||||
if (deletedMessageId != null) {
|
||||
messageIds.remove(deletedMessageId)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
MessageReceiver.handle(message, proto, openGroupID)
|
||||
|
||||
else -> MessageReceiver.handle(message, proto, openGroupID)
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
Log.e(TAG, "Couldn't process message.", e)
|
||||
@ -150,14 +165,20 @@ class BatchMessageReceiveJob(
|
||||
}
|
||||
}
|
||||
// increment unreads, notify, and update thread
|
||||
val unreadFromMine = messageIds.indexOfLast { (_,fromMe) -> fromMe }
|
||||
var trueUnreadCount = messageIds.filter { (_,fromMe) -> !fromMe }.size
|
||||
val unreadFromMine = messageIds.map { it.value.first }.indexOfLast { it }
|
||||
var trueUnreadCount = messageIds.filter { !it.value.first }.size
|
||||
var trueUnreadMentionCount = messageIds.filter { !it.value.first && it.value.second }.size
|
||||
if (unreadFromMine >= 0) {
|
||||
trueUnreadCount -= (unreadFromMine + 1)
|
||||
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)
|
||||
storage.incrementUnread(threadId, trueUnreadCount, trueUnreadMentionCount)
|
||||
}
|
||||
storage.updateThread(threadId, true)
|
||||
SSKEnvironment.shared.notificationManager.updateNotification(context, threadId)
|
||||
|
@ -29,6 +29,7 @@ public class IncomingMediaMessage {
|
||||
private final boolean expirationUpdate;
|
||||
private final boolean unidentified;
|
||||
private final boolean messageRequestResponse;
|
||||
private final boolean hasMention;
|
||||
|
||||
private final DataExtractionNotificationInfoMessage dataExtractionNotification;
|
||||
private final QuoteModel quote;
|
||||
@ -44,6 +45,7 @@ public class IncomingMediaMessage {
|
||||
boolean expirationUpdate,
|
||||
boolean unidentified,
|
||||
boolean messageRequestResponse,
|
||||
boolean hasMention,
|
||||
Optional<String> body,
|
||||
Optional<SignalServiceGroup> group,
|
||||
Optional<List<SignalServiceAttachment>> attachments,
|
||||
@ -63,6 +65,7 @@ public class IncomingMediaMessage {
|
||||
this.quote = quote.orNull();
|
||||
this.unidentified = unidentified;
|
||||
this.messageRequestResponse = messageRequestResponse;
|
||||
this.hasMention = hasMention;
|
||||
|
||||
if (group.isPresent()) this.groupId = Address.fromSerialized(GroupUtil.INSTANCE.getEncodedId(group.get()));
|
||||
else this.groupId = null;
|
||||
@ -81,7 +84,8 @@ public class IncomingMediaMessage {
|
||||
Optional<List<LinkPreview>> linkPreviews)
|
||||
{
|
||||
return new IncomingMediaMessage(from, message.getSentTimestamp(), -1, expiresIn, false,
|
||||
false, false, Optional.fromNullable(message.getText()), group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews, Optional.absent());
|
||||
false, false, message.getHasMention(), Optional.fromNullable(message.getText()),
|
||||
group, Optional.fromNullable(attachments), quote, Optional.absent(), linkPreviews, Optional.absent());
|
||||
}
|
||||
|
||||
public int getSubscriptionId() {
|
||||
@ -124,6 +128,10 @@ public class IncomingMediaMessage {
|
||||
return groupId != null;
|
||||
}
|
||||
|
||||
public boolean hasMention() {
|
||||
return hasMention;
|
||||
}
|
||||
|
||||
public boolean isScreenshotDataExtraction() {
|
||||
if (dataExtractionNotification == null) return false;
|
||||
else {
|
||||
|
@ -43,24 +43,25 @@ public class IncomingTextMessage implements Parcelable {
|
||||
private final long expiresInMillis;
|
||||
private final boolean unidentified;
|
||||
private final int callType;
|
||||
private final boolean hasMention;
|
||||
|
||||
private boolean isOpenGroupInvitation = false;
|
||||
|
||||
public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis,
|
||||
String encodedBody, Optional<SignalServiceGroup> group,
|
||||
long expiresInMillis, boolean unidentified) {
|
||||
this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, unidentified, -1);
|
||||
long expiresInMillis, boolean unidentified, boolean hasMention) {
|
||||
this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, unidentified, -1, hasMention);
|
||||
}
|
||||
|
||||
public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis,
|
||||
String encodedBody, Optional<SignalServiceGroup> group,
|
||||
long expiresInMillis, boolean unidentified, int callType) {
|
||||
this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, unidentified, callType, true);
|
||||
long expiresInMillis, boolean unidentified, int callType, boolean hasMention) {
|
||||
this(sender, senderDeviceId, sentTimestampMillis, encodedBody, group, expiresInMillis, unidentified, callType, hasMention, true);
|
||||
}
|
||||
|
||||
public IncomingTextMessage(Address sender, int senderDeviceId, long sentTimestampMillis,
|
||||
String encodedBody, Optional<SignalServiceGroup> group,
|
||||
long expiresInMillis, boolean unidentified, int callType, boolean isPush) {
|
||||
long expiresInMillis, boolean unidentified, int callType, boolean hasMention, boolean isPush) {
|
||||
this.message = encodedBody;
|
||||
this.sender = sender;
|
||||
this.senderDeviceId = senderDeviceId;
|
||||
@ -74,6 +75,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
this.expiresInMillis = expiresInMillis;
|
||||
this.unidentified = unidentified;
|
||||
this.callType = callType;
|
||||
this.hasMention = hasMention;
|
||||
|
||||
if (group.isPresent()) {
|
||||
this.groupId = Address.fromSerialized(GroupUtil.getEncodedId(group.get()));
|
||||
@ -98,6 +100,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
this.unidentified = in.readInt() == 1;
|
||||
this.isOpenGroupInvitation = in.readInt() == 1;
|
||||
this.callType = in.readInt();
|
||||
this.hasMention = in.readInt() == 1;
|
||||
}
|
||||
|
||||
public IncomingTextMessage(IncomingTextMessage base, String newBody) {
|
||||
@ -116,6 +119,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
this.unidentified = base.isUnidentified();
|
||||
this.isOpenGroupInvitation = base.isOpenGroupInvitation();
|
||||
this.callType = base.callType;
|
||||
this.hasMention = base.hasMention;
|
||||
}
|
||||
|
||||
public static IncomingTextMessage from(VisibleMessage message,
|
||||
@ -123,7 +127,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
Optional<SignalServiceGroup> group,
|
||||
long expiresInMillis)
|
||||
{
|
||||
return new IncomingTextMessage(sender, 1, message.getSentTimestamp(), message.getText(), group, expiresInMillis, false);
|
||||
return new IncomingTextMessage(sender, 1, message.getSentTimestamp(), message.getText(), group, expiresInMillis, false, message.getHasMention());
|
||||
}
|
||||
|
||||
public static IncomingTextMessage fromOpenGroupInvitation(OpenGroupInvitation openGroupInvitation, Address sender, Long sentTimestamp)
|
||||
@ -133,7 +137,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
if (url == null || name == null) { return null; }
|
||||
// FIXME: Doing toJSON() to get the body here is weird
|
||||
String body = UpdateMessageData.Companion.buildOpenGroupInvitation(url, name).toJSON();
|
||||
IncomingTextMessage incomingTextMessage = new IncomingTextMessage(sender, 1, sentTimestamp, body, Optional.absent(), 0, false);
|
||||
IncomingTextMessage incomingTextMessage = new IncomingTextMessage(sender, 1, sentTimestamp, body, Optional.absent(), 0, false, false);
|
||||
incomingTextMessage.isOpenGroupInvitation = true;
|
||||
return incomingTextMessage;
|
||||
}
|
||||
@ -142,7 +146,7 @@ public class IncomingTextMessage implements Parcelable {
|
||||
Address sender,
|
||||
Optional<SignalServiceGroup> group,
|
||||
long sentTimestamp) {
|
||||
return new IncomingTextMessage(sender, 1, sentTimestamp, null, group, 0, false, callMessageType.ordinal(), false);
|
||||
return new IncomingTextMessage(sender, 1, sentTimestamp, null, group, 0, false, callMessageType.ordinal(), false, false);
|
||||
}
|
||||
|
||||
public int getSubscriptionId() {
|
||||
@ -207,6 +211,8 @@ public class IncomingTextMessage implements Parcelable {
|
||||
|
||||
public boolean isOpenGroupInvitation() { return isOpenGroupInvitation; }
|
||||
|
||||
public boolean hasMention() { return hasMention; }
|
||||
|
||||
public boolean isCallInfo() {
|
||||
int callMessageTypeLength = CallMessageType.values().length;
|
||||
return callType >= 0 && callType < callMessageTypeLength;
|
||||
@ -240,5 +246,6 @@ public class IncomingTextMessage implements Parcelable {
|
||||
out.writeInt(unidentified ? 1 : 0);
|
||||
out.writeInt(isOpenGroupInvitation ? 1 : 0);
|
||||
out.writeInt(callType);
|
||||
out.writeInt(hasMention ? 1 : 0);
|
||||
}
|
||||
}
|
||||
|
@ -85,8 +85,8 @@ public class OutgoingMediaMessage {
|
||||
previews = Collections.singletonList(linkPreview);
|
||||
}
|
||||
return new OutgoingMediaMessage(recipient, message.getText(), attachments, message.getSentTimestamp(), -1,
|
||||
recipient.getExpireMessages() * 1000, DistributionTypes.DEFAULT, outgoingQuote, Collections.emptyList(),
|
||||
previews, Collections.emptyList(), Collections.emptyList());
|
||||
recipient.getExpireMessages() * 1000, DistributionTypes.DEFAULT, outgoingQuote,
|
||||
Collections.emptyList(), previews, Collections.emptyList(), Collections.emptyList());
|
||||
}
|
||||
|
||||
public Recipient getRecipient() {
|
||||
|
@ -24,6 +24,7 @@ class VisibleMessage : Message() {
|
||||
var profile: Profile? = null
|
||||
var openGroupInvitation: OpenGroupInvitation? = null
|
||||
var reaction: Reaction? = null
|
||||
var hasMention: Boolean = false
|
||||
|
||||
override val isSelfSendValid: Boolean = true
|
||||
|
||||
|
@ -189,22 +189,24 @@ private fun handleConfigurationMessage(message: ConfigurationMessage) {
|
||||
storage.addContacts(message.contacts)
|
||||
}
|
||||
|
||||
fun MessageReceiver.handleUnsendRequest(message: UnsendRequest) {
|
||||
fun MessageReceiver.handleUnsendRequest(message: UnsendRequest): Long? {
|
||||
val userPublicKey = MessagingModuleConfiguration.shared.storage.getUserPublicKey()
|
||||
if (message.sender != message.author && (message.sender != userPublicKey && userPublicKey != null)) { return }
|
||||
if (message.sender != message.author && (message.sender != userPublicKey && userPublicKey != null)) { return null }
|
||||
val context = MessagingModuleConfiguration.shared.context
|
||||
val storage = MessagingModuleConfiguration.shared.storage
|
||||
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
|
||||
val timestamp = message.timestamp ?: return
|
||||
val author = message.author ?: return
|
||||
val messageIdToDelete = storage.getMessageIdInDatabase(timestamp, author) ?: return
|
||||
val timestamp = message.timestamp ?: return null
|
||||
val author = message.author ?: return null
|
||||
val messageIdToDelete = storage.getMessageIdInDatabase(timestamp, author) ?: return null
|
||||
messageDataProvider.getServerHashForMessage(messageIdToDelete)?.let { serverHash ->
|
||||
SnodeAPI.deleteMessage(author, listOf(serverHash))
|
||||
}
|
||||
messageDataProvider.updateMessageAsDeleted(timestamp, author)
|
||||
val deletedMessageId = messageDataProvider.updateMessageAsDeleted(timestamp, author)
|
||||
if (!messageDataProvider.isOutgoingMessage(messageIdToDelete)) {
|
||||
SSKEnvironment.shared.notificationManager.updateNotification(context)
|
||||
}
|
||||
|
||||
return deletedMessageId
|
||||
}
|
||||
|
||||
fun handleMessageRequestResponse(message: MessageRequestResponse) {
|
||||
@ -264,6 +266,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage,
|
||||
}
|
||||
// Parse quote if needed
|
||||
var quoteModel: QuoteModel? = null
|
||||
var quoteMessageBody: String? = null
|
||||
if (message.quote != null && proto.dataMessage.hasQuote()) {
|
||||
val quote = proto.dataMessage.quote
|
||||
|
||||
@ -275,6 +278,7 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage,
|
||||
|
||||
val messageDataProvider = MessagingModuleConfiguration.shared.messageDataProvider
|
||||
val messageInfo = messageDataProvider.getMessageForQuote(quote.id, author)
|
||||
quoteMessageBody = messageInfo?.third
|
||||
quoteModel = if (messageInfo != null) {
|
||||
val attachments = if (messageInfo.second) messageDataProvider.getAttachmentsAndLinkPreviewFor(messageInfo.first) else ArrayList()
|
||||
QuoteModel(quote.id, author,null,false, attachments)
|
||||
@ -321,6 +325,20 @@ fun MessageReceiver.handleVisibleMessage(message: VisibleMessage,
|
||||
storage.removeReaction(reaction.emoji!!, reaction.timestamp!!, reaction.publicKey!!, threadIsGroup)
|
||||
}
|
||||
} ?: run {
|
||||
// A user is mentioned if their public key is in the body of a message or one of their messages
|
||||
// was quoted
|
||||
val messageText = message.text
|
||||
message.hasMention = listOf(userPublicKey, userBlindedKey)
|
||||
.filterNotNull()
|
||||
.any { key ->
|
||||
return@any (
|
||||
messageText != null &&
|
||||
messageText.contains("@$key")
|
||||
) || (
|
||||
(quoteModel?.author?.serialize() ?: "") == key
|
||||
)
|
||||
}
|
||||
|
||||
// Persist the message
|
||||
message.threadID = threadID
|
||||
val messageID =
|
||||
|
Loading…
x
Reference in New Issue
Block a user