Added the unread mention indicator to the conversation list

Fixed the unread indicator colours to match correct theming designs
Fixed a bug where the unread count could be incorrect when receiving UnsendRequests within the same poll
Added a couple missing theme colours
This commit is contained in:
Morgan Pretty
2023-01-17 16:30:05 +11:00
parent cae15a200d
commit 694ca79958
30 changed files with 335 additions and 179 deletions

View File

@@ -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> {
@@ -184,16 +184,18 @@ class DatabaseAttachmentProvider(context: Context, helper: SQLCipherOpenHelper)
DatabaseComponent.get(context).lokiMessageDatabase().deleteMessageServerHash(messageID)
}
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? {

View File

@@ -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);

View File

@@ -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)
@@ -1272,7 +1276,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
)
}
@@ -1316,6 +1320,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
}
@@ -1333,7 +1338,7 @@ class MmsDatabase(context: Context, databaseHelper: SQLCipherOpenHelper) : Messa
dateSent, dateReceived, deliveryReceiptCount, threadId,
contentLocationBytes, messageSize, expiry, status,
transactionIdBytes, mailbox, slideDeck,
readReceiptCount
readReceiptCount, hasMention
)
}
@@ -1367,6 +1372,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
}
@@ -1403,7 +1409,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
)
}
@@ -1596,5 +1602,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;"
}
}

View File

@@ -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;

View File

@@ -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);
@@ -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);

View File

@@ -121,6 +121,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();
@@ -206,14 +209,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);
}
@@ -444,6 +450,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());
@@ -462,7 +469,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) {
@@ -736,7 +743,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);
}
}
@@ -777,6 +784,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;
@@ -790,7 +798,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) {

View File

@@ -101,9 +101,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) {
@@ -458,7 +458,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()
@@ -721,6 +721,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
false,
false,
false,
false,
Optional.absent(),
Optional.absent(),
Optional.absent(),
@@ -795,6 +796,7 @@ class Storage(context: Context, helper: SQLCipherOpenHelper) : Database(context,
false,
false,
true,
false,
Optional.absent(),
Optional.absent(),
Optional.absent(),

View File

@@ -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);
}

View File

@@ -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();

View File

@@ -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;
}

View File

@@ -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;
}

View File

@@ -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);

View File

@@ -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;

View File

@@ -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() {

View File

@@ -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();
}

View File

@@ -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

View File

@@ -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)

View File

@@ -111,6 +111,7 @@ public class ExpiringMessageManager implements SSKEnvironment.MessageExpirationM
duration * 1000L, true,
false,
false,
false,
Optional.absent(),
groupInfo,
Optional.absent(),

View File

@@ -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,

View File

@@ -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>

View File

@@ -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>

View File

@@ -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">@color/classic_dark_4</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>
@@ -475,13 +475,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>
@@ -507,7 +507,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>
@@ -532,15 +532,15 @@
<!-- 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="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>
@@ -556,26 +556,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>
@@ -584,7 +584,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>
@@ -595,48 +595,48 @@
<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="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="message_sent_text_color">@color/ocean_light_1</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:textColorPrimary</item>
<item name="message_selected">@color/ocean_light_5</item>
<item name="message_selected">@color/ocean_light_6</item>
</style>
</resources>