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

@@ -21,7 +21,7 @@ interface MessageDataProvider {
*/
fun getMessageID(serverId: Long, threadId: Long): Pair<Long, Boolean>?
fun deleteMessage(messageID: 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?
@@ -36,7 +36,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>

View File

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

View File

@@ -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
@@ -108,25 +106,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)
@@ -139,14 +154,15 @@ 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
val trueUnreadMentionCount = messageIds.filter { !it.value.first && it.value.second }.size
if (unreadFromMine >= 0) {
trueUnreadCount -= (unreadFromMine + 1)
storage.markConversationAsRead(threadId, false)
}
if (trueUnreadCount > 0) {
storage.incrementUnread(threadId, trueUnreadCount)
storage.incrementUnread(threadId, trueUnreadCount, trueUnreadMentionCount)
}
storage.updateThread(threadId, true)
SSKEnvironment.shared.notificationManager.updateNotification(context, threadId)

View File

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

View File

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

View File

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

View File

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

View File

@@ -173,22 +173,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) {
@@ -248,6 +250,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
@@ -259,6 +262,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)
@@ -305,6 +309,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 =