diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt index 38d4b23236..95e1e28ee0 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/ConversationAdapter.kt @@ -138,6 +138,7 @@ class ConversationAdapter( viewHolder.view.bind( message = message, previous = messageBefore, + threadRecipient = threadRecipientProvider(), longPress = { onItemLongPress(message, viewHolder.adapterPosition, viewHolder.view) } ) } diff --git a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt index 5e4f71b93b..4cc0276c5e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/ControlMessageView.kt @@ -29,6 +29,7 @@ import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.getColorFromAttr import org.session.libsession.utilities.isGroup import org.session.libsession.utilities.isGroupOrCommunity +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.displayName import org.thoughtcrime.securesms.auth.LoginStateRepository import org.thoughtcrime.securesms.conversation.disappearingmessages.DisappearingMessages @@ -76,6 +77,7 @@ class ControlMessageView : LinearLayout { @Inject lateinit var recipientRepository: RecipientRepository @Inject lateinit var loginStateRepository: LoginStateRepository @Inject lateinit var threadDatabase: ThreadDatabase + @Inject lateinit var messageFormatter: MessageFormatter val controlContentView: View get() = binding.controlContentView @@ -83,13 +85,20 @@ class ControlMessageView : LinearLayout { layoutParams = RecyclerView.LayoutParams(RecyclerView.LayoutParams.MATCH_PARENT, RecyclerView.LayoutParams.WRAP_CONTENT) } - fun bind(message: MessageRecord, previous: MessageRecord?, longPress: (() -> Unit)? = null) { + fun bind(message: MessageRecord, + threadRecipient: Recipient, + previous: MessageRecord?, + longPress: (() -> Unit)? = null) { binding.dateBreakTextView.showDateBreak(message, previous, dateUtils) binding.iconImageView.isGone = true binding.expirationTimerView.isGone = true binding.followSetting.isGone = true - var messageBody: CharSequence = message.getDisplayBody(context) + val messageBody = messageFormatter.formatMessageBody( + context = context, + message = message, + threadRecipient = threadRecipient, + ) binding.root.contentDescription = null binding.textView.text = messageBody @@ -99,9 +108,7 @@ class ControlMessageView : LinearLayout { binding.apply { expirationTimerView.isVisible = true - val threadRecipient = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(message.threadId) - - if (threadRecipient?.isGroup == true) { + if (threadRecipient.isGroupRecipient) { expirationTimerView.setTimerIcon() } else { expirationTimerView.setExpirationTime(message.expireStarted, message.expiresIn) @@ -110,7 +117,7 @@ class ControlMessageView : LinearLayout { followSetting.isVisible = ExpirationConfiguration.isNewConfigEnabled && !message.isOutgoing && messageContent.expiryMode != (message.individualRecipient?.expiryMode ?: ExpiryMode.NONE) - && threadRecipient?.isGroupOrCommunity != true + && !threadRecipient.isGroupOrCommunityRecipient if (followSetting.isVisible) { binding.controlContentView.setOnClickListener { @@ -138,20 +145,6 @@ class ControlMessageView : LinearLayout { } } message.isMessageRequestResponse -> { - val msgRecipient = message.recipient.address.toString() - val me = loginStateRepository.getLocalNumber() - binding.textView.text = if (me == msgRecipient) { // you accepted the user's request - threadDatabase.getRecipientForThreadId(message.threadId) - ?.let { recipientRepository.getRecipientSync(it) } - ?.let { recipient -> context.getSubbedCharSequence( - R.string.messageRequestYouHaveAccepted, - NAME_KEY to recipient.displayName() - ) - } - } else { // they accepted your request - context.getString(R.string.messageRequestsAccepted) - } - binding.root.contentDescription = context.getString(R.string.AccessibilityId_message_request_config_message) } message.isCallLog -> { diff --git a/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/MessageFormatter.kt similarity index 67% rename from app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt rename to app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/MessageFormatter.kt index 5a1231151e..d9d6aa0759 100644 --- a/app/src/main/java/org/session/libsession/messaging/utilities/UpdateMessageBuilder.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/conversation/v2/messages/MessageFormatter.kt @@ -1,50 +1,219 @@ -package org.session.libsession.messaging.utilities +package org.thoughtcrime.securesms.conversation.v2.messages import android.content.Context +import android.text.Spannable +import android.text.SpannableString +import android.text.style.ForegroundColorSpan import com.squareup.phrase.Phrase import network.loki.messenger.R import network.loki.messenger.libsession_util.util.ExpiryMode -import org.session.libsession.messaging.MessagingModuleConfiguration import org.session.libsession.messaging.calls.CallMessageType -import org.session.libsession.messaging.calls.CallMessageType.CALL_FIRST_MISSED -import org.session.libsession.messaging.calls.CallMessageType.CALL_INCOMING -import org.session.libsession.messaging.calls.CallMessageType.CALL_MISSED -import org.session.libsession.messaging.calls.CallMessageType.CALL_OUTGOING import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage -import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED -import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage.Kind.SCREENSHOT +import org.session.libsession.messaging.utilities.UpdateMessageData import org.session.libsession.utilities.Address +import org.session.libsession.utilities.Address.Companion.toAddress import org.session.libsession.utilities.ConfigFactoryProtocol import org.session.libsession.utilities.ExpirationUtil +import org.session.libsession.utilities.StringSubstitutionConstants.AUTHOR_KEY import org.session.libsession.utilities.StringSubstitutionConstants.COUNT_KEY import org.session.libsession.utilities.StringSubstitutionConstants.DISAPPEARING_MESSAGES_TYPE_KEY import org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY +import org.session.libsession.utilities.StringSubstitutionConstants.MESSAGE_SNIPPET_KEY import org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.OTHER_NAME_KEY import org.session.libsession.utilities.StringSubstitutionConstants.TIME_KEY +import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.getGroup +import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.displayName import org.session.libsignal.utilities.AccountId import org.session.libsignal.utilities.Log +import org.thoughtcrime.securesms.auth.LoginStateRepository +import org.thoughtcrime.securesms.database.RecipientRepository +import org.thoughtcrime.securesms.database.model.GroupThreadStatus +import org.thoughtcrime.securesms.database.model.MessageRecord +import org.thoughtcrime.securesms.database.model.ThreadRecord +import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate +import org.thoughtcrime.securesms.ui.getSubbedCharSequence +import javax.inject.Inject -object UpdateMessageBuilder { - const val TAG = "UpdateMessageBuilder" +class MessageFormatter @Inject constructor( + private val configFactory: ConfigFactoryProtocol, + private val recipientRepository: RecipientRepository, + private val loginStateRepository: LoginStateRepository, +) { - val storage = MessagingModuleConfiguration.shared.storage - val recipientRepository = MessagingModuleConfiguration.shared.recipientRepository + fun formatMessageBody( + context: Context, + message: MessageRecord, + threadRecipient: Recipient + ): CharSequence { + when { + message.isGroupUpdateMessage -> { + val updateMessageData: UpdateMessageData = message.getGroupUpdateMessage() ?: return "" - private fun getGroupMemberName(senderAddress: String): String { - return recipientRepository.getRecipientSync(Address.fromSerialized(senderAddress)) - .displayName() + val text = SpannableString( + buildGroupUpdateMessage( + context = context, + updateMessageData = updateMessageData, + isOutgoing = message.isOutgoing, + messageTimestamp = message.timestamp, + expireStarted = message.expireStarted + ) + ) + + if (updateMessageData.isGroupErrorQuitKind()) { + text.setSpan( + ForegroundColorSpan(ThemeUtil.getThemedColor(context, R.attr.danger)), + 0, + text.length, + Spannable.SPAN_INCLUSIVE_EXCLUSIVE + ) + } else if (updateMessageData.isGroupLeavingKind()) { + text.setSpan( + ForegroundColorSpan( + ThemeUtil.getThemedColor( + context, + android.R.attr.textColorTertiary + ) + ), 0, text.length, Spannable.SPAN_INCLUSIVE_EXCLUSIVE + ) + } + + return text + } + message.messageContent is DisappearingMessageUpdate -> { + val isGroup = threadRecipient.isGroupOrCommunityRecipient + return buildExpirationTimerMessage( + context, + (message.messageContent as DisappearingMessageUpdate).expiryMode, + isGroup, + message.individualRecipient, + message.isOutgoing + ) + } + message.isDataExtractionNotification -> { + if (message.isScreenshotNotification) return SpannableString( + buildDataExtractionMessage( + context = context, + kind = DataExtractionNotificationInfoMessage.Kind.SCREENSHOT, + sender = message.individualRecipient + ) + ) + else if (message.isMediaSavedNotification) return SpannableString( + buildDataExtractionMessage( + context = context, + kind = DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED, + sender = message.individualRecipient + ) + ) + } + message.isCallLog -> { + val callType = if (message.isIncomingCall) { + CallMessageType.CALL_INCOMING + } else if (message.isOutgoingCall) { + CallMessageType.CALL_OUTGOING + } else if (message.isMissedCall) { + CallMessageType.CALL_MISSED + } else { + CallMessageType.CALL_FIRST_MISSED + } + + return SpannableString( + buildCallMessage( + context = context, + type = callType, + sender = message.individualRecipient + ) + ) + } + message.isMessageRequestResponse -> { + return if (message.recipient.isSelf) { + // you accepted the user's request + context.getSubbedCharSequence( + R.string.messageRequestYouHaveAccepted, + NAME_KEY to threadRecipient.displayName() + ) + } else { + // the user accepted your request + context.getString(R.string.messageRequestsAccepted) + } + } + } + + return SpannableString(message.body) + } + + fun formatThreadSnippet( + context: Context, + thread: ThreadRecord + ): CharSequence { + val lastMessage = thread.lastMessage + + return when { + thread.groupThreadStatus == GroupThreadStatus.Kicked -> { + Phrase.from(context, R.string.groupRemovedYou) + .put(GROUP_NAME_KEY, thread.recipient.displayName()) + .format() + .toString() + } + + thread.groupThreadStatus == GroupThreadStatus.Destroyed -> { + Phrase.from(context, R.string.groupDeletedMemberDescription) + .put(GROUP_NAME_KEY, thread.recipient.displayName()) + .format() + .toString() + } + + lastMessage == null -> { + // no need to display anything if there are no messages + "" + } + + // We will show different text for community invitation on the thread list + lastMessage.isOpenGroupInvitation -> { + context.getString(R.string.communityInvitation) + } + + else -> { + val text = formatMessageBody( + context = context, + message = lastMessage, + threadRecipient = thread.recipient + ) + + when { + // There are certain messages that we want to keep their formatting + lastMessage.groupUpdateMessage?.isGroupLeavingKind() == true || + lastMessage.groupUpdateMessage?.isGroupErrorQuitKind() == true -> { + text + } + + // For group/community threads, we want to prefix the snippet with the author's name + thread.recipient.isGroupOrCommunityRecipient -> { + val prefix = if (lastMessage.isOutgoing) { + context.getString(R.string.you) + } else { + lastMessage.individualRecipient.displayName() + } + + Phrase.from(context.getString(R.string.messageSnippetGroup)) + .put(AUTHOR_KEY, prefix) + .put(MESSAGE_SNIPPET_KEY, text.toString()) + .format() + } + + // For all other messages, convert to plain string to avoid weird snippet appearances + else -> text.toString() + } + } + } } - @JvmStatic - fun buildGroupUpdateMessage( + private fun buildGroupUpdateMessage( context: Context, - groupV2Id: AccountId?, // null for legacy groups updateMessageData: UpdateMessageData, - configFactory: ConfigFactoryProtocol, isOutgoing: Boolean, messageTimestamp: Long, expireStarted: Long, @@ -103,14 +272,14 @@ object UpdateMessageBuilder { // --- Group member(s) removed --- is UpdateMessageData.Kind.GroupMemberRemoved -> { - val userPublicKey = storage.getUserPublicKey()!! + val userPublicKey = loginStateRepository.requireLocalNumber() // 1st case: you are part of the removed members return if (userPublicKey in updateData.updatedMembers) { if (isOutgoing) context.getText(R.string.groupMemberYouLeft) // You chose to leave else Phrase.from(context, R.string.groupRemovedYou) // You were forced to leave - .put(GROUP_NAME_KEY, updateData.groupName) - .format() + .put(GROUP_NAME_KEY, updateData.groupName) + .format() } else // 2nd case: you are not part of the removed members { @@ -120,7 +289,7 @@ object UpdateMessageBuilder { 0 -> { Log.w(TAG, "Somehow you asked to remove zero members.") "" // Return an empty string - we don't want to show the error in the conversation - } + } 1 -> Phrase.from(context, R.string.groupRemoved) .put(NAME_KEY, getGroupMemberName(updateData.updatedMembers.elementAt(0))) .format() @@ -129,9 +298,9 @@ object UpdateMessageBuilder { .put(OTHER_NAME_KEY, getGroupMemberName(updateData.updatedMembers.elementAt(1))) .format() else -> Phrase.from(context, R.string.groupRemovedMultiple) - .put(NAME_KEY, getGroupMemberName(updateData.updatedMembers.elementAt(0))) - .put(COUNT_KEY, updateData.updatedMembers.size - 1) - .format() + .put(NAME_KEY, getGroupMemberName(updateData.updatedMembers.elementAt(0))) + .put(COUNT_KEY, updateData.updatedMembers.size - 1) + .format() } } else // b.) Someone else is the person doing the removing of one or more members @@ -184,15 +353,18 @@ object UpdateMessageBuilder { } is UpdateMessageData.Kind.GroupAvatarUpdated -> context.getString(R.string.groupDisplayPictureUpdated) is UpdateMessageData.Kind.GroupExpirationUpdated -> { - buildExpirationTimerMessage(context, updateData.updatedExpiration, isGroup = true, - senderId = updateData.updatingAdmin, + buildExpirationTimerMessage( + context = context, + duration = updateData.updatedExpiration, + isGroup = true, + sender = recipientRepository.getRecipientSync(updateData.updatingAdmin.toAddress()), isOutgoing = isOutgoing, timestamp = messageTimestamp, expireStarted = expireStarted ) } is UpdateMessageData.Kind.GroupMemberUpdated -> { - val userPublicKey = storage.getUserPublicKey()!! + val userPublicKey = loginStateRepository.requireLocalNumber() val number = updateData.sessionIds.size val containsUser = updateData.sessionIds.contains(userPublicKey) val historyShared = updateData.historyShared @@ -207,7 +379,7 @@ object UpdateMessageBuilder { .put(NAME_KEY, getGroupMemberName(updateData.sessionIds.first())) .format() number == 2 && containsUser -> Phrase.from(context, - if (historyShared) R.string.groupMemberNewYouHistoryTwo else R.string.groupInviteYouAndOtherNew) + if (historyShared) R.string.groupMemberNewYouHistoryTwo else R.string.groupInviteYouAndOtherNew) .put(OTHER_NAME_KEY, getGroupMemberName(updateData.sessionIds.first { it != userPublicKey })) .format() number == 2 -> Phrase.from(context, @@ -319,21 +491,19 @@ object UpdateMessageBuilder { } } + private fun getGroupMemberName(senderAddress: String): String { + return recipientRepository.getRecipientSync(Address.fromSerialized(senderAddress)) + .displayName() + } + fun buildExpirationTimerMessage( context: Context, mode: ExpiryMode, isGroup: Boolean, // Note: isGroup should cover both closed groups AND communities - senderId: String?, + sender: Recipient, isOutgoing: Boolean, ): CharSequence { - if (!isOutgoing && senderId == null) { - Log.w(TAG, "buildExpirationTimerMessage: Cannot build for outgoing message when senderId is null.") - return "" - } - - val senderName = if (isOutgoing) context.getString(R.string.you) else getGroupMemberName( - senderId!! - ) + val senderName = if (isOutgoing) context.getString(R.string.you) else sender.displayName() // Case 1.) Disappearing messages have been turned off.. if (mode == ExpiryMode.NONE) { @@ -390,13 +560,40 @@ object UpdateMessageBuilder { } } + fun buildDataExtractionMessage(context: Context, + kind: DataExtractionNotificationInfoMessage.Kind, + sender: Recipient): CharSequence { + + return when (kind) { + DataExtractionNotificationInfoMessage.Kind.SCREENSHOT -> Phrase.from(context, R.string.screenshotTaken) + .put(NAME_KEY, sender.displayName()) + .format() + + DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED -> Phrase.from(context, R.string.attachmentsMediaSaved) + .put(NAME_KEY, sender.displayName()) + .format() + } + } + + fun buildCallMessage(context: Context, type: CallMessageType, sender: Recipient): String { + return when (type) { + CallMessageType.CALL_INCOMING -> Phrase.from(context, R.string.callsCalledYou).put(NAME_KEY, sender.displayName()) + .format().toString() + + CallMessageType.CALL_OUTGOING -> Phrase.from(context, R.string.callsYouCalled).put(NAME_KEY, sender.displayName()) + .format().toString() + + CallMessageType.CALL_MISSED, CallMessageType.CALL_FIRST_MISSED -> Phrase.from(context, R.string.callsMissedCallFrom) + .put(NAME_KEY, sender.displayName()).format().toString() + } + } @Deprecated("Use the version with ExpiryMode instead. This will be removed in a future release.") fun buildExpirationTimerMessage( context: Context, duration: Long, isGroup: Boolean, // Note: isGroup should cover both closed groups AND communities - senderId: String? = null, + sender: Recipient, isOutgoing: Boolean = false, timestamp: Long, expireStarted: Long @@ -409,41 +606,12 @@ object UpdateMessageBuilder { else -> ExpiryMode.AfterRead(duration) }, isGroup = isGroup, - senderId = senderId, + sender = sender, isOutgoing = isOutgoing, ) } - fun buildDataExtractionMessage(context: Context, - kind: DataExtractionNotificationInfoMessage.Kind, - senderId: String? = null): CharSequence { - - val senderName = if (senderId != null) getGroupMemberName(senderId) else context.getString(R.string.unknown) - - return when (kind) { - SCREENSHOT -> Phrase.from(context, R.string.screenshotTaken) - .put(NAME_KEY, senderName) - .format() - - MEDIA_SAVED -> Phrase.from(context, R.string.attachmentsMediaSaved) - .put(NAME_KEY, senderName) - .format() - } - } - - fun buildCallMessage(context: Context, type: CallMessageType, senderId: String): String { - val senderName = recipientRepository.getRecipientSync(Address.fromSerialized(senderId)) - .displayName() - - return when (type) { - CALL_INCOMING -> Phrase.from(context, R.string.callsCalledYou).put(NAME_KEY, senderName) - .format().toString() - - CALL_OUTGOING -> Phrase.from(context, R.string.callsYouCalled).put(NAME_KEY, senderName) - .format().toString() - - CALL_MISSED, CALL_FIRST_MISSED -> Phrase.from(context, R.string.callsMissedCallFrom) - .put(NAME_KEY, senderName).format().toString() - } + companion object { + private const val TAG = "MessageFormatter" } -} +} \ No newline at end of file diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java index 286979289b..bbc4372315 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/DisplayRecord.java @@ -68,7 +68,6 @@ public abstract class DisplayRecord { public @NonNull String getBody() { return body == null ? "" : body; } - public abstract CharSequence getDisplayBody(@NonNull Context context); public Recipient getRecipient() { return recipient; } public long getDateSent() { return dateSent; } public long getDateReceived() { return dateReceived; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java index ce04203b84..577f6dad6b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/MessageRecord.java @@ -16,33 +16,17 @@ */ package org.thoughtcrime.securesms.database.model; -import android.content.Context; -import android.text.Spannable; -import android.text.SpannableString; -import android.text.style.ForegroundColorSpan; - import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import org.session.libsession.messaging.MessagingModuleConfiguration; -import org.session.libsession.messaging.calls.CallMessageType; -import org.session.libsession.messaging.sending_receiving.data_extraction.DataExtractionNotificationInfoMessage; -import org.session.libsession.messaging.utilities.UpdateMessageBuilder; import org.session.libsession.messaging.utilities.UpdateMessageData; -import org.session.libsession.utilities.Address; -import org.session.libsession.utilities.AddressKt; -import org.session.libsession.utilities.ThemeUtil; import org.session.libsession.utilities.recipients.Recipient; -import org.session.libsignal.utilities.AccountId; -import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate; import org.thoughtcrime.securesms.database.model.content.MessageContent; -import org.thoughtcrime.securesms.dependencies.DatabaseComponent; import java.util.List; import java.util.Objects; import java.util.Set; -import network.loki.messenger.R; import network.loki.messenger.libsession_util.protocol.ProFeature; /** @@ -128,59 +112,6 @@ public UpdateMessageData getGroupUpdateMessage() { return groupUpdateMessage; } - @Override - public CharSequence getDisplayBody(@NonNull Context context) { - if (isGroupUpdateMessage()) { - UpdateMessageData updateMessageData = getGroupUpdateMessage(); - Address groupRecipient = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(getThreadId()); - - if (updateMessageData == null || groupRecipient == null) { - return ""; - } - - SpannableString text = new SpannableString(UpdateMessageBuilder.buildGroupUpdateMessage( - context, - AddressKt.isGroupV2(groupRecipient) ? new AccountId(groupRecipient.toString()) : null, // accountId is only used for GroupsV2 - updateMessageData, - MessagingModuleConfiguration.getShared().getConfigFactory(), - isOutgoing(), - getTimestamp(), - getExpireStarted()) - ); - - if (updateMessageData.isGroupErrorQuitKind()) { - text.setSpan(new ForegroundColorSpan(ThemeUtil.getThemedColor(context, R.attr.danger)), 0, text.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - } else if (updateMessageData.isGroupLeavingKind()) { - text.setSpan(new ForegroundColorSpan(ThemeUtil.getThemedColor(context, android.R.attr.textColorTertiary)), 0, text.length(), Spannable.SPAN_INCLUSIVE_EXCLUSIVE); - } - - return text; - } else if (getMessageContent() instanceof DisappearingMessageUpdate) { - Address rec = DatabaseComponent.get(context).threadDatabase().getRecipientForThreadId(getThreadId()); - if(rec == null) return ""; - boolean isGroup = AddressKt.isGroupOrCommunity(rec); - return UpdateMessageBuilder.INSTANCE - .buildExpirationTimerMessage(context, ((DisappearingMessageUpdate) getMessageContent()).getExpiryMode(), isGroup, getIndividualRecipient().getAddress().toString(), isOutgoing()); - } else if (isDataExtractionNotification()) { - if (isScreenshotNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.SCREENSHOT, getIndividualRecipient().getAddress().toString()))); - else if (isMediaSavedNotification()) return new SpannableString((UpdateMessageBuilder.INSTANCE.buildDataExtractionMessage(context, DataExtractionNotificationInfoMessage.Kind.MEDIA_SAVED, getIndividualRecipient().getAddress().toString()))); - } else if (isCallLog()) { - CallMessageType callType; - if (isIncomingCall()) { - callType = CallMessageType.CALL_INCOMING; - } else if (isOutgoingCall()) { - callType = CallMessageType.CALL_OUTGOING; - } else if (isMissedCall()) { - callType = CallMessageType.CALL_MISSED; - } else { - callType = CallMessageType.CALL_FIRST_MISSED; - } - return new SpannableString(UpdateMessageBuilder.INSTANCE.buildCallMessage(context, callType, getIndividualRecipient().getAddress().toString())); - } - - return new SpannableString(getBody()); - } - public boolean isGroupExpirationTimerUpdate() { if (!isGroupUpdateMessage()) { return false; diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java index f9aa2e2846..6cb467c35d 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/SmsMessageRecord.java @@ -17,10 +17,6 @@ package org.thoughtcrime.securesms.database.model; -import android.content.Context; - -import androidx.annotation.NonNull; - import org.session.libsession.utilities.recipients.Recipient; import java.util.List; @@ -56,10 +52,6 @@ public long getType() { return type; } - @Override - public CharSequence getDisplayBody(@NonNull Context context) { - return super.getDisplayBody(context); - } @Override public boolean isMms() { diff --git a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java index 5c7f2f073e..5ed38dc3ef 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java +++ b/app/src/main/java/org/thoughtcrime/securesms/database/model/ThreadRecord.java @@ -17,34 +17,21 @@ */ package org.thoughtcrime.securesms.database.model; -import static org.session.libsession.utilities.StringSubstitutionConstants.APP_NAME_KEY; import static org.session.libsession.utilities.StringSubstitutionConstants.AUTHOR_KEY; -import static org.session.libsession.utilities.StringSubstitutionConstants.GROUP_NAME_KEY; import static org.session.libsession.utilities.StringSubstitutionConstants.MESSAGE_SNIPPET_KEY; -import static org.session.libsession.utilities.StringSubstitutionConstants.NAME_KEY; import android.content.Context; -import android.text.SpannableString; -import android.text.TextUtils; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import com.squareup.phrase.Phrase; -import org.session.libsession.messaging.utilities.UpdateMessageData; import org.session.libsession.utilities.AddressKt; -import org.session.libsession.utilities.TextSecurePreferences; import org.session.libsession.utilities.recipients.Recipient; import org.session.libsession.utilities.recipients.RecipientNamesKt; -import org.thoughtcrime.securesms.ApplicationContext; -import org.thoughtcrime.securesms.database.MmsSmsColumns; -import org.thoughtcrime.securesms.database.SmsDatabase; -import org.thoughtcrime.securesms.database.model.content.DisappearingMessageUpdate; import org.thoughtcrime.securesms.database.model.content.MessageContent; -import org.thoughtcrime.securesms.ui.UtilKt; -import kotlin.Pair; import network.loki.messenger.R; /** @@ -54,173 +41,37 @@ * */ public class ThreadRecord extends DisplayRecord { - - public @Nullable final MessageRecord lastMessage; - private final long count; - private final int unreadCount; - private final int unreadMentionCount; - private final long lastSeen; - private final String invitingAdminId; - private final boolean isUnread; - - @NonNull - private final GroupThreadStatus groupThreadStatus; - - public ThreadRecord(@NonNull String body, - @Nullable MessageRecord lastMessage, @NonNull Recipient recipient, long date, long count, int unreadCount, - int unreadMentionCount, long threadId, int deliveryReceiptCount, int status, - long snippetType, - long lastSeen, int readReceiptCount, String invitingAdminId, - @NonNull GroupThreadStatus groupThreadStatus, - @Nullable MessageContent messageContent, - boolean isUnread) - { - super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount, messageContent); - this.lastMessage = lastMessage; - this.count = count; - this.unreadCount = unreadCount; - this.unreadMentionCount = unreadMentionCount; - this.lastSeen = lastSeen; - this.invitingAdminId = invitingAdminId; - this.groupThreadStatus = groupThreadStatus; - this.isUnread = isUnread; - } - - private String getName() { - return RecipientNamesKt.displayName(getRecipient()); - } - - @Override - public CharSequence getDisplayBody(@NonNull Context context) { - if (groupThreadStatus == GroupThreadStatus.Kicked) { - return Phrase.from(context, R.string.groupRemovedYou) - .put(GROUP_NAME_KEY, getName()) - .format() - .toString(); - } else if (groupThreadStatus == GroupThreadStatus.Destroyed) { - return Phrase.from(context, R.string.groupDeletedMemberDescription) - .put(GROUP_NAME_KEY, getName()) - .format() - .toString(); - } else if (lastMessage == null){ - // no need to display anything if there are no messages - return ""; - } - else if (isGroupUpdateMessage()) { - CharSequence body = lastMessage.getDisplayBody(context); - UpdateMessageData updatedMessage = lastMessage.getGroupUpdateMessage(); - - // For group leaving and error quit messages, we will leave the message as formatted - if (updatedMessage != null && (updatedMessage.isGroupLeavingKind() || updatedMessage.isGroupErrorQuitKind())) { - return body; - } - - // Otherwise we'll need to remove all the formatting and just display the text - return body.toString(); - } else if (isOpenGroupInvitation()) { - return context.getString(R.string.communityInvitation); - } else if (MmsSmsColumns.Types.isLegacyType(type)) { - return Phrase.from(context, R.string.messageErrorOld) - .put(APP_NAME_KEY, context.getString(R.string.app_name)) - .format().toString(); - } else if (MmsSmsColumns.Types.isDraftMessageType(type)) { - String draftText = context.getString(R.string.draft); - return draftText + " " + getBody(); - } else if (SmsDatabase.Types.isOutgoingCall(type)) { - return Phrase.from(context, R.string.callsYouCalled) - .put(NAME_KEY, getName()) - .format().toString(); - } else if (SmsDatabase.Types.isIncomingCall(type)) { - return Phrase.from(context, R.string.callsCalledYou) - .put(NAME_KEY, getName()) - .format().toString(); - } else if (SmsDatabase.Types.isMissedCall(type)) { - return Phrase.from(context, R.string.callsMissedCallFrom) - .put(NAME_KEY, getName()) - .format().toString(); - } else if (getMessageContent() instanceof DisappearingMessageUpdate) { - // Use the same message as we would for displaying on the conversation screen. - // lastMessage shouldn't be null here, but we'll check just in case. - if (lastMessage != null) { - return lastMessage.getDisplayBody(context).toString(); - } else { - return ""; - } - } - else if (MmsSmsColumns.Types.isMediaSavedExtraction(type)) { - return Phrase.from(context, R.string.attachmentsMediaSaved) - .put(NAME_KEY, getName()) - .format().toString(); - - } else if (MmsSmsColumns.Types.isScreenshotExtraction(type)) { - return Phrase.from(context, R.string.screenshotTaken) - .put(NAME_KEY, getName()) - .format().toString(); - - } else if (MmsSmsColumns.Types.isMessageRequestResponse(type)) { - try { - if (lastMessage.getRecipient().getAddress().toString().equals( - ((ApplicationContext) context.getApplicationContext()).getLoginStateRepository() - .get().getLocalNumber())) { - return UtilKt.getSubbedCharSequence( - context, - R.string.messageRequestYouHaveAccepted, - new Pair<>(NAME_KEY, getName()) - ); - } - } - catch (Exception e){} // the above can throw a null exception - - return context.getString(R.string.messageRequestsAccepted); - } else if (getCount() == 0) { - return new SpannableString(context.getString(R.string.messageEmpty)); - } else { - // This block hits when we receive a media message from an unaccepted contact - however, - // unaccepted contacts aren't allowed to send us media - so we'll return an empty string - // if it's JUST an image, or the body text that accompanied the image should any exist. - // We could return null here - but then we have to find all the usages of this - // `getDisplayBody` method and make sure it doesn't fall over if it has a null result. - if (TextUtils.isEmpty(getBody())) { - return new SpannableString(""); - // Old behaviour was: return new SpannableString(emphasisAdded(context.getString(R.string.mediaMessage))); - } else { - return getNonControlMessageDisplayBody(context); - } - } + public @Nullable final MessageRecord lastMessage; + private final long count; + private final int unreadCount; + private final int unreadMentionCount; + private final long lastSeen; + private final String invitingAdminId; + private final boolean isUnread; + + @NonNull + public final GroupThreadStatus groupThreadStatus; + + public ThreadRecord(@NonNull String body, + @Nullable MessageRecord lastMessage, @NonNull Recipient recipient, long date, long count, int unreadCount, + int unreadMentionCount, long threadId, int deliveryReceiptCount, int status, + long snippetType, + long lastSeen, int readReceiptCount, String invitingAdminId, + @NonNull GroupThreadStatus groupThreadStatus, + @Nullable MessageContent messageContent, + boolean isUnread) + { + super(body, recipient, date, date, threadId, status, deliveryReceiptCount, snippetType, readReceiptCount, messageContent); + this.lastMessage = lastMessage; + this.count = count; + this.unreadCount = unreadCount; + this.unreadMentionCount = unreadMentionCount; + this.lastSeen = lastSeen; + this.invitingAdminId = invitingAdminId; + this.groupThreadStatus = groupThreadStatus; + this.isUnread = isUnread; } - /** - * Logic to get the body for non control messages - */ - public CharSequence getNonControlMessageDisplayBody(@NonNull Context context) { - Recipient recipient = getRecipient(); - // The logic will differ depending on the type. - // 1-1, note to self and control messages (we shouldn't have any in here, but leaving the - // logic to be safe) do not need author details - if (recipient.isLocalNumber() || !AddressKt.isGroupOrCommunity(recipient.getAddress()) || - (lastMessage != null && lastMessage.isControlMessage()) - ) { - return getBody(); - } else { // for groups (new, legacy, communities) show either 'You' or the contact's name - String prefix = ""; - if (lastMessage != null && lastMessage.isOutgoing()) { - prefix = context.getString(R.string.you); - } - else if(lastMessage != null){ - prefix = RecipientNamesKt.displayName(lastMessage.getIndividualRecipient()); - } - - return Phrase.from(context.getString(R.string.messageSnippetGroup)) - .put(AUTHOR_KEY, prefix) - .put(MESSAGE_SNIPPET_KEY, getBody()) - .format().toString(); - } - } - - @Override - public boolean isGroupUpdateMessage() { - return lastMessage != null && lastMessage.isGroupUpdateMessage(); - } public long getCount() { return count; } diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt index b571d0828d..e64503fa73 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/ConversationView.kt @@ -15,6 +15,7 @@ import network.loki.messenger.databinding.ViewConversationBinding import org.session.libsession.utilities.ThemeUtil import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.displayName +import org.thoughtcrime.securesms.conversation.v2.messages.MessageFormatter import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.model.NotifyType @@ -35,6 +36,7 @@ class ConversationView : LinearLayout { @Inject lateinit var proStatusManager: ProStatusManager @Inject lateinit var avatarUtils: AvatarUtils @Inject lateinit var recipientRepository: RecipientRepository + @Inject lateinit var messageFormatter: MessageFormatter private val binding: ViewConversationBinding by lazy { ViewConversationBinding.bind(this) } private val screenWidth = Resources.getSystem().displayMetrics.widthPixels @@ -105,7 +107,10 @@ class ConversationView : LinearLayout { binding.muteIndicatorImageView.setImageResource(drawableRes) val snippet = highlightMentions( - text = thread.getDisplayBody(context), + text = messageFormatter.formatThreadSnippet( + context = context, + thread = thread, + ), formatOnly = true, // no styling here, only text formatting recipientRepository = recipientRepository, context = context diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt index 2c0b5410f8..eb5c55a74b 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeActivity.kt @@ -62,6 +62,7 @@ import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.ScreenLockActionBarActivity import org.thoughtcrime.securesms.auth.LoginStateRepository import org.thoughtcrime.securesms.conversation.v2.ConversationActivityV2 +import org.thoughtcrime.securesms.conversation.v2.messages.MessageFormatter import org.thoughtcrime.securesms.conversation.v2.settings.notification.NotificationSettingsActivity import org.thoughtcrime.securesms.crypto.IdentityKeyUtil import org.thoughtcrime.securesms.database.GroupDatabase @@ -142,6 +143,7 @@ class HomeActivity : ScreenLockActionBarActivity(), @Inject lateinit var recipientRepository: RecipientRepository @Inject lateinit var avatarUtils: AvatarUtils @Inject lateinit var loginStateRepository: LoginStateRepository + @Inject lateinit var messageFormatter: MessageFormatter private val globalSearchViewModel by viewModels() private val homeViewModel by viewModels() @@ -150,7 +152,13 @@ class HomeActivity : ScreenLockActionBarActivity(), private val publicKey: String by lazy { loginStateRepository.requireLocalNumber() } private val homeAdapter: HomeAdapter by lazy { - HomeAdapter(context = this, configFactory = configFactory, listener = this, ::showMessageRequests, ::hideMessageRequests) + HomeAdapter( + context = this, + messageFormatter = messageFormatter, + listener = this, + showMessageRequests = ::showMessageRequests, + hideMessageRequests = ::hideMessageRequests, + ) } private val globalSearchAdapter by lazy { diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt index b5d4e6bbb9..35c9d01637 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeAdapter.kt @@ -10,14 +10,14 @@ import androidx.recyclerview.widget.RecyclerView.NO_ID import com.bumptech.glide.RequestManager import network.loki.messenger.R import network.loki.messenger.databinding.ViewMessageRequestBannerBinding -import org.thoughtcrime.securesms.dependencies.ConfigFactory +import org.thoughtcrime.securesms.conversation.v2.messages.MessageFormatter class HomeAdapter( private val context: Context, - private val configFactory: ConfigFactory, private val listener: ConversationClickListener, private val showMessageRequests: () -> Unit, private val hideMessageRequests: () -> Unit, + private val messageFormatter: MessageFormatter, ) : RecyclerView.Adapter() { companion object { @@ -29,7 +29,7 @@ class HomeAdapter( set(newData) { if (field === newData) return - val diff = HomeDiffUtil(field, newData, context, configFactory) + val diff = HomeDiffUtil(field, newData, context, messageFormatter) val diffResult = DiffUtil.calculateDiff(diff) field = newData diffResult.dispatchUpdatesTo(this) diff --git a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt index 7d3b80f05f..0749b6838e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/home/HomeDiffUtil.kt @@ -2,14 +2,13 @@ package org.thoughtcrime.securesms.home import android.content.Context import androidx.recyclerview.widget.DiffUtil -import org.thoughtcrime.securesms.dependencies.ConfigFactory -import org.thoughtcrime.securesms.util.getConversationUnread +import org.thoughtcrime.securesms.conversation.v2.messages.MessageFormatter class HomeDiffUtil( private val old: HomeViewModel.Data, private val new: HomeViewModel.Data, private val context: Context, - private val configFactory: ConfigFactory + private val messageFormatter: MessageFormatter, ): DiffUtil.Callback() { override fun getOldListSize(): Int = old.items.size @@ -60,7 +59,8 @@ class HomeDiffUtil( if (isSameItem) { isSameItem = (oldItem.recipient == newItem.recipient) } // Note: Two instances of 'SpannableString' may not equate even though their content matches - if (isSameItem) { isSameItem = (oldItem.getDisplayBody(context).toString() == newItem.getDisplayBody(context).toString()) } + if (isSameItem) { isSameItem = (messageFormatter.formatThreadSnippet(context, oldItem).toString() + == messageFormatter.formatThreadSnippet(context, newItem).toString()) } if (isSameItem) { isSameItem = ( diff --git a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt index 617fd51087..a49cc8b47e 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/messagerequests/MessageRequestView.kt @@ -14,6 +14,7 @@ import network.loki.messenger.R import network.loki.messenger.databinding.ViewMessageRequestBinding import org.session.libsession.utilities.recipients.Recipient import org.session.libsession.utilities.recipients.displayName +import org.thoughtcrime.securesms.conversation.v2.messages.MessageFormatter import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.database.RecipientRepository import org.thoughtcrime.securesms.database.model.ThreadRecord @@ -45,6 +46,9 @@ class MessageRequestView : LinearLayout { @Inject lateinit var recipientRepository: RecipientRepository + @Inject + lateinit var messageFormatter: MessageFormatter + // region Lifecycle constructor(context: Context) : super(context) { initialize() } constructor(context: Context, attrs: AttributeSet) : super(context, attrs) { initialize() } @@ -94,7 +98,7 @@ class MessageRequestView : LinearLayout { val snippet = highlightMentions( recipientRepository = recipientRepository, - text = thread.getDisplayBody(context), + text = messageFormatter.formatThreadSnippet(context, thread), formatOnly = true, // no styling here, only text formatting context = context ) diff --git a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt index c1d398a451..b530a8c0ab 100644 --- a/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt +++ b/app/src/main/java/org/thoughtcrime/securesms/notifications/DefaultMessageNotifier.kt @@ -25,6 +25,7 @@ import androidx.core.app.NotificationCompat import androidx.core.app.NotificationManagerCompat import coil3.ImageLoader import com.squareup.phrase.Phrase +import dagger.Lazy import network.loki.messenger.R import network.loki.messenger.libsession_util.util.BlindKeyAPI import org.session.libsession.messaging.sending_receiving.notifications.MessageNotifier @@ -40,6 +41,7 @@ import org.session.libsignal.utilities.Hex import org.session.libsignal.utilities.IdPrefix import org.session.libsignal.utilities.Log import org.thoughtcrime.securesms.auth.LoginStateRepository +import org.thoughtcrime.securesms.conversation.v2.messages.MessageFormatter import org.thoughtcrime.securesms.conversation.v2.utilities.MentionUtilities.highlightMentions import org.thoughtcrime.securesms.database.MmsSmsColumns.NOTIFIED import org.thoughtcrime.securesms.database.MmsSmsDatabase @@ -75,6 +77,7 @@ class DefaultMessageNotifier @Inject constructor( private val mmsSmsDatabase: MmsSmsDatabase, private val imageLoader: Provider, private val loginStateRepository: LoginStateRepository, + private val messageFormatter: Lazy, ) : MessageNotifier { override fun setVisibleThread(threadId: Long) { visibleThread = threadId @@ -572,21 +575,22 @@ class DefaultMessageNotifier @Inject constructor( if (record == null) break // Bail if there are no more MessageRecords val threadId = record.threadId - val threadRecipients = if (threadId != -1L) { + val threadRecipient = if (threadId != -1L) { threadDatabase.getRecipientForThreadId(threadId) ?.let(recipientRepository::getRecipientSync) } else null + if (threadRecipient == null) continue + // Start by checking various scenario that we should skip // Skip if muted or calls - if (threadRecipients?.isMuted() == true) continue + if (threadRecipient.isMuted()) continue if (record.isIncomingCall || record.isOutgoingCall) continue // Handle message requests early - val isMessageRequest = threadRecipients != null && - !threadRecipients.isGroupOrCommunityRecipient && - !threadRecipients.approved && + val isMessageRequest = !threadRecipient.isGroupOrCommunityRecipient && + !threadRecipient.approved && !threadDatabase.getLastSeenAndHasSent(threadId).second() // Do not repeat request notifications once the thread has >1 messages @@ -597,12 +601,18 @@ class DefaultMessageNotifier @Inject constructor( } // Check notification settings - if (threadRecipients?.notifyType == NotifyType.NONE) continue + if (threadRecipient.notifyType == NotifyType.NONE) continue val userPublicKey = loginStateRepository.requireLocalNumber() + var body = messageFormatter.get().formatMessageBody( + context = context, + message = record, + threadRecipient = threadRecipient, + ) + // Check mentions-only setting - if (threadRecipients?.notifyType == NotifyType.MENTIONS) { + if (threadRecipient.notifyType == NotifyType.MENTIONS) { var blindedPublicKey = cache[threadId] if (blindedPublicKey == null) { blindedPublicKey = generateBlindedId(threadId, context) @@ -610,7 +620,6 @@ class DefaultMessageNotifier @Inject constructor( } var isMentioned = false - val body = record.getDisplayBody(context).toString() // Check for @mentions if (body.contains("@$userPublicKey") || @@ -658,7 +667,6 @@ class DefaultMessageNotifier @Inject constructor( } // Prepare message body - var body: CharSequence = record.getDisplayBody(context) var slideDeck: SlideDeck? = null if (isMessageRequest) { @@ -695,7 +703,7 @@ class DefaultMessageNotifier @Inject constructor( record.isMms || record.isMmsNotification, record.individualRecipient, record.recipient, - threadRecipients, + threadRecipient, threadId, body, record.timestamp, @@ -708,8 +716,7 @@ class DefaultMessageNotifier @Inject constructor( // Only if: it's OUR message AND it has reactions AND it's NOT an unread incoming message else if (record.isOutgoing && hasUnreadReactions && - threadRecipients != null && - !threadRecipients.isGroupOrCommunityRecipient + !threadRecipient.isGroupOrCommunityRecipient ) { var blindedPublicKey = cache[threadId] @@ -750,7 +757,7 @@ class DefaultMessageNotifier @Inject constructor( record.isMms || record.isMmsNotification, reactor, reactor, - threadRecipients, + threadRecipient, threadId, emoji, latestReaction.dateSent, null,