Manage display name disambiguation (Fixes #172)

This commit is contained in:
Benoit Marty 2019-06-18 15:44:11 +02:00
parent b1f5b3ad96
commit 625500212d
11 changed files with 39 additions and 22 deletions

View File

@ -71,6 +71,7 @@ data class Event(
@Json(name = "content") val content: Content? = null, @Json(name = "content") val content: Content? = null,
@Json(name = "prev_content") val prevContent: Content? = null, @Json(name = "prev_content") val prevContent: Content? = null,
@Json(name = "origin_server_ts") val originServerTs: Long? = null, @Json(name = "origin_server_ts") val originServerTs: Long? = null,
// MatrixId of the sender (TODO rename)
@Json(name = "sender") val sender: String? = null, @Json(name = "sender") val sender: String? = null,
@Json(name = "state_key") val stateKey: String? = null, @Json(name = "state_key") val stateKey: String? = null,
@Json(name = "room_id") val roomId: String? = null, @Json(name = "room_id") val roomId: String? = null,

View File

@ -31,6 +31,7 @@ data class TimelineEvent(
val localId: String, val localId: String,
val displayIndex: Int, val displayIndex: Int,
val senderName: String?, val senderName: String?,
val isUniqueDisplayName: Boolean,
val senderAvatar: String?, val senderAvatar: String?,
val sendState: SendState, val sendState: SendState,
val annotations: EventAnnotationsSummary? = null val annotations: EventAnnotationsSummary? = null
@ -53,6 +54,18 @@ data class TimelineEvent(
} }
} }
fun getDisambiguatedDisplayName(): String {
return if (isUniqueDisplayName) {
senderName ?: root.sender
} else {
senderName?.let {
it + " (" + root.sender + ")"
}
?: root.sender
}
?: ""
}
/** /**
* Get the metadata associated with a key. * Get the metadata associated with a key.
* @param key the key to get the metadata * @param key the key to get the metadata

View File

@ -54,7 +54,7 @@ internal class RoomMembers(private val realm: Realm,
} }
return EventEntity return EventEntity
.where(realm, roomId, EventType.STATE_ROOM_MEMBER) .where(realm, roomId, EventType.STATE_ROOM_MEMBER)
.contains(EventEntityFields.CONTENT, displayName) .contains(EventEntityFields.CONTENT, "\"displayname\":\"$displayName\"")
.distinct(EventEntityFields.STATE_KEY) .distinct(EventEntityFields.STATE_KEY)
.findAll() .findAll()
.size == 1 .size == 1

View File

@ -26,6 +26,7 @@ import im.vector.matrix.android.internal.crypto.MXEventDecryptionResult
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.session.room.EventRelationExtractor import im.vector.matrix.android.internal.session.room.EventRelationExtractor
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor
import io.realm.Realm import io.realm.Realm
import timber.log.Timber import timber.log.Timber
@ -49,7 +50,11 @@ internal class TimelineEventFactory(
val cacheKey = sender + eventEntity.localId val cacheKey = sender + eventEntity.localId
val senderData = senderCache.getOrPut(cacheKey) { val senderData = senderCache.getOrPut(cacheKey) {
val senderRoomMember = roomMemberExtractor.extractFrom(eventEntity, realm) val senderRoomMember = roomMemberExtractor.extractFrom(eventEntity, realm)
SenderData(senderRoomMember?.displayName, senderRoomMember?.avatarUrl) val isUniqueDisplayName = RoomMembers(realm, eventEntity.roomId).isUniqueDisplayName(senderRoomMember?.displayName)
SenderData(senderRoomMember?.displayName,
isUniqueDisplayName,
senderRoomMember?.avatarUrl)
} }
val event = eventEntity.asDomain() val event = eventEntity.asDomain()
if (event.getClearType() == EventType.ENCRYPTED) { if (event.getClearType() == EventType.ENCRYPTED) {
@ -62,6 +67,7 @@ internal class TimelineEventFactory(
eventEntity.localId, eventEntity.localId,
eventEntity.displayIndex, eventEntity.displayIndex,
senderData.senderName, senderData.senderName,
senderData.isUniqueDisplayName,
senderData.senderAvatar, senderData.senderAvatar,
eventEntity.sendState, eventEntity.sendState,
relations relations
@ -96,7 +102,7 @@ internal class TimelineEventFactory(
private data class SenderData( private data class SenderData(
val senderName: String?, val senderName: String?,
val isUniqueDisplayName: Boolean,
val senderAvatar: String? val senderAvatar: String?
) )
} }

View File

@ -222,7 +222,7 @@ class RoomDetailFragment :
} }
//switch to expanded bar //switch to expanded bar
composerLayout.composerRelatedMessageTitle.apply { composerLayout.composerRelatedMessageTitle.apply {
text = event.senderName text = event.getDisambiguatedDisplayName()
setTextColor(ContextCompat.getColor(requireContext(), getColorFromUserId(event.root.sender))) setTextColor(ContextCompat.getColor(requireContext(), getColorFromUserId(event.root.sender)))
} }

View File

@ -282,7 +282,7 @@ class RoomDetailViewModel(initialState: RoomDetailViewState,
val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault()) val dateFormat = SimpleDateFormat("EEE, d MMM yyyy HH:mm", Locale.getDefault())
_nonBlockingPopAlert.postValue(LiveEvent( _nonBlockingPopAlert.postValue(LiveEvent(
Pair(R.string.last_edited_info_message, listOf( Pair(R.string.last_edited_info_message, listOf(
lastReplace.senderName ?: "?", lastReplace.getDisambiguatedDisplayName(),
dateFormat.format(Date(lastReplace.root.originServerTs ?: 0))) dateFormat.format(Date(lastReplace.root.originServerTs ?: 0)))
)) ))
) )

View File

@ -50,7 +50,7 @@ class ViewReactionViewModel(private val session: Session,
sum.sourceEvents.mapNotNull { room.getTimeLineEvent(it) }.forEach { sum.sourceEvents.mapNotNull { room.getTimeLineEvent(it) }.forEach {
val localDate = it.root.localDateTime() val localDate = it.root.localDateTime()
results.add(ReactionInfo(it.root.eventId!!, sum.key, it.root.sender results.add(ReactionInfo(it.root.eventId!!, sum.key, it.root.sender
?: "", it.senderName, timelineDateFormatter.formatMessageHour(localDate))) ?: "", it.getDisambiguatedDisplayName(), timelineDateFormatter.formatMessageHour(localDate)))
} }
} }
setState { setState {

View File

@ -32,13 +32,13 @@ class NoticeEventFormatter(private val stringProvider: StringProvider) {
fun format(timelineEvent: TimelineEvent): CharSequence? { fun format(timelineEvent: TimelineEvent): CharSequence? {
return when (val type = timelineEvent.root.getClearType()) { return when (val type = timelineEvent.root.getClearType()) {
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.senderName) EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.senderName) EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderName()) EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderName())
EventType.STATE_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderName) EventType.STATE_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
EventType.CALL_INVITE, EventType.CALL_INVITE,
EventType.CALL_HANGUP, EventType.CALL_HANGUP,
EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.senderName) EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName())
else -> { else -> {
Timber.v("Type $type not handled by this formatter") Timber.v("Type $type not handled by this formatter")
null null

View File

@ -85,11 +85,10 @@ fun TimelineEvent.senderAvatar(): String? {
fun TimelineEvent.senderName(): String? { fun TimelineEvent.senderName(): String? {
// We might have no senderName when user leave, so we try to get it from prevContent // We might have no senderName when user leave, so we try to get it from prevContent
return senderName return when {
?: if (root.type == EventType.STATE_ROOM_MEMBER) { senderName != null -> getDisambiguatedDisplayName()
root.prevContent.toModel<RoomMember>()?.displayName root.type == EventType.STATE_ROOM_MEMBER -> root.prevContent.toModel<RoomMember>()?.displayName
} else { else -> null
null
} }
} }

View File

@ -83,7 +83,6 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : BaseEventItem<H>() {
override fun bind(holder: H) { override fun bind(holder: H) {
super.bind(holder) super.bind(holder)
if (informationData.showInformation) { if (informationData.showInformation) {
holder.avatarImageView.layoutParams = holder.avatarImageView.layoutParams?.apply { holder.avatarImageView.layoutParams = holder.avatarImageView.layoutParams?.apply {
val size = dpToPx(avatarStyle.avatarSizeDP, holder.view.context) val size = dpToPx(avatarStyle.avatarSizeDP, holder.view.context)
height = size height = size

View File

@ -22,7 +22,6 @@ import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.core.resources.ColorProvider import im.vector.riotredesign.core.resources.ColorProvider
import im.vector.riotredesign.features.home.getColorFromUserId import im.vector.riotredesign.features.home.getColorFromUserId
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderAvatar
import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotredesign.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotredesign.features.home.room.detail.timeline.item.ReactionInfoData import im.vector.riotredesign.features.home.room.detail.timeline.item.ReactionInfoData
import me.gujun.android.span.span import me.gujun.android.span.span
@ -38,21 +37,21 @@ class MessageInformationDataFactory(private val timelineDateFormatter: TimelineD
val eventId = event.root.eventId!! val eventId = event.root.eventId!!
val date = event.root.localDateTime() val date = event.root.localDateTime()
val nextDate = nextEvent?.root?.localDateTime() val nextDate = nextEvent?.root?.localDateTime()
val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate() val addDaySeparator = date.toLocalDate() != nextDate?.toLocalDate()
val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60)) val isNextMessageReceivedMoreThanOneHourAgo = nextDate?.isBefore(date.minusMinutes(60))
?: false ?: false
val showInformation = val showInformation =
addDaySeparator addDaySeparator
|| event.senderAvatar != nextEvent?.senderAvatar || event.senderAvatar != nextEvent?.senderAvatar
|| event.senderName != nextEvent?.senderName || event.getDisambiguatedDisplayName() != nextEvent?.getDisambiguatedDisplayName()
|| (nextEvent?.root?.getClearType() != EventType.MESSAGE && nextEvent?.root?.getClearType() != EventType.ENCRYPTED) || (nextEvent?.root?.getClearType() != EventType.MESSAGE && nextEvent?.root?.getClearType() != EventType.ENCRYPTED)
|| isNextMessageReceivedMoreThanOneHourAgo || isNextMessageReceivedMoreThanOneHourAgo
val time = timelineDateFormatter.formatMessageHour(date) val time = timelineDateFormatter.formatMessageHour(date)
val avatarUrl = event.senderAvatar() val avatarUrl = event.senderAvatar
val memberName = event.senderName ?: event.root.sender ?: "" val memberName = event.getDisambiguatedDisplayName()
val formattedMemberName = span(memberName) { val formattedMemberName = span(memberName) {
textColor = colorProvider.getColor(getColorFromUserId(event.root.sender textColor = colorProvider.getColor(getColorFromUserId(event.root.sender
?: "")) ?: ""))