Improve group name coloring performance.

This commit is contained in:
Cody Henthorne
2023-07-25 19:12:04 -04:00
committed by GitHub
parent ded29619cd
commit 3731723472
6 changed files with 75 additions and 20 deletions

View File

@@ -1,11 +1,12 @@
package org.thoughtcrime.securesms.conversation.colors
import androidx.annotation.NonNull
import org.thoughtcrime.securesms.database.GroupTable
import org.thoughtcrime.securesms.database.SignalDatabase
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.groups.GroupId
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.push.ServiceId
/**
* Class to assist managing the colors of author names in the UI in groups.
@@ -17,11 +18,40 @@ class GroupAuthorNameColorHelper {
/** Needed so that we have a full history of current *and* past members (so colors don't change when someone leaves) */
private val fullMemberCache: MutableMap<GroupId, Set<Recipient>> = mutableMapOf()
private val fullMemberServiceIdsCache: MutableMap<GroupId, Set<ServiceId>> = mutableMapOf()
/**
* Given a [GroupRecord], returns a map of member -> name color.
*/
fun getColorMap(groupRecord: GroupRecord): Map<RecipientId, NameColor> {
if (!groupRecord.isV2Group) {
return getColorMap(groupRecord.id)
}
val cachedServiceIds: Set<ServiceId> = fullMemberServiceIdsCache[groupRecord.id] ?: setOf()
val allIds: Set<ServiceId> = cachedServiceIds + groupRecord.decryptedMemberServiceIds.toSet()
fullMemberServiceIdsCache[groupRecord.id] = allIds
val selfId = Recipient.self().requireServiceId()
val members: List<ServiceId> = allIds
.filter { it != selfId }
.sortedBy { it.toString() }
val allColors: List<NameColor> = ChatColorsPalette.Names.all
val colors: MutableMap<RecipientId, NameColor> = HashMap()
for (i in members.indices) {
colors[RecipientId.from(members[i])] = allColors[i % allColors.size]
}
return colors.toMap()
}
/**
* Given a [GroupId], returns a map of member -> name color.
*/
fun getColorMap(@NonNull groupId: GroupId): Map<RecipientId, NameColor> {
fun getColorMap(groupId: GroupId): Map<RecipientId, NameColor> {
val dbMembers: Set<Recipient> = SignalDatabase
.groups
.getGroupMembers(groupId, GroupTable.MemberSet.FULL_MEMBERS_INCLUDING_SELF)

View File

@@ -1060,6 +1060,8 @@ class ConversationFragment :
}
composeText.setMessageSendType(MessageSendType.SignalMessageSendType)
colorizer.onNameColorsChanged(inputReadyState.groupNameColors)
}
private fun presentIdentityRecordsState(identityRecordsState: IdentityRecordsState) {

View File

@@ -18,7 +18,6 @@ import io.reactivex.rxjava3.android.schedulers.AndroidSchedulers
import io.reactivex.rxjava3.core.Completable
import io.reactivex.rxjava3.core.Flowable
import io.reactivex.rxjava3.core.Maybe
import io.reactivex.rxjava3.core.Observable
import io.reactivex.rxjava3.core.Single
import io.reactivex.rxjava3.core.SingleEmitter
import io.reactivex.rxjava3.schedulers.Schedulers
@@ -164,19 +163,10 @@ class ConversationRepository(
* Generates the name color-map for groups.
*/
fun getNameColorsMap(
recipient: Recipient,
group: GroupRecord,
groupAuthorNameColorHelper: GroupAuthorNameColorHelper
): Observable<Map<RecipientId, NameColor>> {
return Recipient.observable(recipient.id)
.distinctUntilChanged { a, b -> a.participantIds == b.participantIds }
.map {
if (it.groupId.isPresent) {
groupAuthorNameColorHelper.getColorMap(it.requireGroupId())
} else {
emptyMap()
}
}
.subscribeOn(Schedulers.io())
): Map<RecipientId, NameColor> {
return groupAuthorNameColorHelper.getColorMap(group)
}
fun sendReactionRemoval(messageRecord: MessageRecord, oldRecord: ReactionRecord): Completable {

View File

@@ -104,10 +104,14 @@ class ConversationViewModel(
val pagingController = ProxyPagingController<ConversationElementKey>()
val nameColorsMap: Observable<Map<RecipientId, NameColor>> = recipient
.filter { it.isGroup }
.flatMap { repository.getNameColorsMap(it, groupAuthorNameColorHelper) }
val nameColorsMap: Observable<Map<RecipientId, NameColor>> = recipientRepository
.groupRecord
.filter { it.isPresent }
.map { it.get() }
.distinctUntilChanged { previous, next -> previous.hasSameMembers(next) }
.map { repository.getNameColorsMap(it, groupAuthorNameColorHelper) }
.distinctUntilChanged()
.observeOn(AndroidSchedulers.mainThread())
@Volatile
var recipientSnapshot: Recipient? = null
@@ -210,6 +214,7 @@ class ConversationViewModel(
conversationRecipient = recipient,
messageRequestState = messageRequestRepository.getMessageRequestState(recipient, threadId),
groupRecord = groupRecord.orNull(),
groupNameColors = groupRecord.map { repository.getNameColorsMap(it, groupAuthorNameColorHelper) }.orElse(emptyMap()),
isClientExpired = SignalStore.misc().isClientDeprecated,
isUnauthorized = TextSecurePreferences.isUnauthorizedReceived(ApplicationDependencies.getApplication())
)

View File

@@ -5,11 +5,13 @@
package org.thoughtcrime.securesms.conversation.v2
import org.thoughtcrime.securesms.conversation.colors.NameColor
import org.thoughtcrime.securesms.database.GroupTable
import org.thoughtcrime.securesms.database.RecipientTable
import org.thoughtcrime.securesms.database.model.GroupRecord
import org.thoughtcrime.securesms.messagerequests.MessageRequestState
import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
/**
* Information necessary for rendering compose input.
@@ -19,7 +21,8 @@ data class InputReadyState(
val messageRequestState: MessageRequestState,
val groupRecord: GroupRecord?,
val isClientExpired: Boolean,
val isUnauthorized: Boolean
val isUnauthorized: Boolean,
val groupNameColors: Map<RecipientId, NameColor>
) {
private val selfMemberLevel: GroupTable.MemberLevel? = groupRecord?.memberLevel(Recipient.self())

View File

@@ -13,7 +13,8 @@ import org.thoughtcrime.securesms.recipients.Recipient
import org.thoughtcrime.securesms.recipients.RecipientId
import org.whispersystems.signalservice.api.groupsv2.DecryptedGroupUtil
import org.whispersystems.signalservice.api.push.DistributionId
import java.lang.AssertionError
import org.whispersystems.signalservice.api.push.ServiceId
import org.whispersystems.signalservice.api.util.UuidUtil
import java.util.Optional
class GroupRecord(
@@ -44,6 +45,22 @@ class GroupRecord(
}
}
/** Valid for v2 groups only */
val decryptedMemberServiceIds: List<ServiceId> by lazy {
if (isV2Group) {
requireV2GroupProperties()
.decryptedGroup
.membersList
.asSequence()
.map { DecryptedGroupUtil.toUuid(it) }
.filterNot { it == UuidUtil.UNKNOWN_UUID }
.map { ServiceId.from(it) }
.toList()
} else {
emptyList()
}
}
/** V1 members that were lost during the V1->V2 migration */
val unmigratedV1Members: List<RecipientId> by lazy {
if (serializedUnmigratedV1Members.isNullOrEmpty()) {
@@ -183,4 +200,12 @@ class GroupRecord(
}
return false
}
fun hasSameMembers(other: GroupRecord): Boolean {
if (!isV2Group || !other.isV2Group) {
return false
}
return decryptedMemberServiceIds == other.decryptedMemberServiceIds
}
}