Manage display name disambiguation (Fixes #172)
This commit is contained in:
parent
b1f5b3ad96
commit
625500212d
|
@ -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,
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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?
|
||||||
)
|
)
|
||||||
|
|
||||||
}
|
}
|
|
@ -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)))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -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)))
|
||||||
))
|
))
|
||||||
)
|
)
|
||||||
|
|
|
@ -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 {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -85,12 +85,11 @@ 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
|
}
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun TimelineEvent.canBeMerged(): Boolean {
|
fun TimelineEvent.canBeMerged(): Boolean {
|
||||||
|
|
|
@ -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
|
||||||
|
|
|
@ -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
|
||||||
?: ""))
|
?: ""))
|
||||||
|
|
Loading…
Reference in New Issue