Uploads: use SenderInfo in TimelineEvent

This commit is contained in:
Benoit Marty 2020-05-20 12:19:30 +02:00
parent f3a5fb7fe3
commit 2adafbeb03
15 changed files with 60 additions and 70 deletions

View File

@ -14,20 +14,22 @@
* limitations under the License. * limitations under the License.
*/ */
package im.vector.matrix.android.api.session.room.uploads package im.vector.matrix.android.api.session.room.sender
// TODO Maybe use this model for TimelineEvent as well data class SenderInfo(
data class UploadSenderInfo( val userId: String,
val senderId: String, /**
val senderName: String?, * Consider using [getDisambiguatedDisplayName]
*/
val displayName: String?,
val isUniqueDisplayName: Boolean, val isUniqueDisplayName: Boolean,
val senderAvatar: String? val avatarUrl: String?
) { ) {
fun getDisambiguatedDisplayName(): String { fun getDisambiguatedDisplayName(): String {
return when { return when {
senderName.isNullOrBlank() -> senderId displayName.isNullOrBlank() -> userId
isUniqueDisplayName -> senderName isUniqueDisplayName -> displayName
else -> "$senderName (${senderId})" else -> "$displayName (${userId})"
} }
} }
} }

View File

@ -26,6 +26,7 @@ import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageStickerContent import im.vector.matrix.android.api.session.room.model.message.MessageStickerContent
import im.vector.matrix.android.api.session.room.model.message.isReply import im.vector.matrix.android.api.session.room.model.message.isReply
import im.vector.matrix.android.api.session.room.sender.SenderInfo
import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply import im.vector.matrix.android.api.util.ContentUtils.extractUsefulTextFromReply
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
@ -39,9 +40,7 @@ data class TimelineEvent(
val localId: Long, val localId: Long,
val eventId: String, val eventId: String,
val displayIndex: Int, val displayIndex: Int,
val senderName: String?, val senderInfo: SenderInfo,
val isUniqueDisplayName: Boolean,
val senderAvatar: String?,
val annotations: EventAnnotationsSummary? = null, val annotations: EventAnnotationsSummary? = null,
val readReceipts: List<ReadReceipt> = emptyList() val readReceipts: List<ReadReceipt> = emptyList()
) { ) {
@ -69,14 +68,6 @@ data class TimelineEvent(
} }
} }
fun getDisambiguatedDisplayName(): String {
return when {
senderName.isNullOrBlank() -> root.senderId ?: ""
isUniqueDisplayName -> senderName
else -> "$senderName (${root.senderId})"
}
}
/** /**
* 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

@ -18,6 +18,7 @@ package im.vector.matrix.android.api.session.room.uploads
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent
import im.vector.matrix.android.api.session.room.sender.SenderInfo
/** /**
* Wrapper around on Event. * Wrapper around on Event.
@ -27,5 +28,5 @@ data class UploadEvent(
val root: Event, val root: Event,
val eventId: String, val eventId: String,
val contentWithAttachmentContent: MessageWithAttachmentContent, val contentWithAttachmentContent: MessageWithAttachmentContent,
val uploadSenderInfo: UploadSenderInfo val senderInfo: SenderInfo
) )

View File

@ -21,6 +21,7 @@ import im.vector.matrix.android.api.session.group.model.GroupSummary
import im.vector.matrix.android.api.session.room.model.RoomMemberSummary import im.vector.matrix.android.api.session.room.model.RoomMemberSummary
import im.vector.matrix.android.api.session.room.model.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom import im.vector.matrix.android.api.session.room.model.roomdirectory.PublicRoom
import im.vector.matrix.android.api.session.room.sender.SenderInfo
import im.vector.matrix.android.api.session.user.model.User import im.vector.matrix.android.api.session.user.model.User
import java.util.Locale import java.util.Locale
@ -154,3 +155,5 @@ fun RoomSummary.toRoomAliasMatrixItem() = MatrixItem.RoomAliasItem(canonicalAlia
fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl) fun PublicRoom.toMatrixItem() = MatrixItem.RoomItem(roomId, name ?: getPrimaryAlias() ?: "", avatarUrl)
fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl) fun RoomMemberSummary.toMatrixItem() = MatrixItem.UserItem(userId, displayName, avatarUrl)
fun SenderInfo.toMatrixItem() = MatrixItem.UserItem(userId, getDisambiguatedDisplayName(), avatarUrl)

View File

@ -18,7 +18,7 @@ package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.events.model.Event import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.ReadReceipt import im.vector.matrix.android.api.session.room.model.ReadReceipt
import im.vector.matrix.android.api.session.room.sender.SenderInfo
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.database.model.TimelineEventEntity import im.vector.matrix.android.internal.database.model.TimelineEventEntity
import javax.inject.Inject import javax.inject.Inject
@ -41,9 +41,12 @@ internal class TimelineEventMapper @Inject constructor(private val readReceiptsS
annotations = timelineEventEntity.annotations?.asDomain(), annotations = timelineEventEntity.annotations?.asDomain(),
localId = timelineEventEntity.localId, localId = timelineEventEntity.localId,
displayIndex = timelineEventEntity.displayIndex, displayIndex = timelineEventEntity.displayIndex,
senderName = timelineEventEntity.senderName, senderInfo = SenderInfo(
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName, userId = timelineEventEntity.root?.sender ?: "",
senderAvatar = timelineEventEntity.senderAvatar, displayName = timelineEventEntity.senderName,
isUniqueDisplayName = timelineEventEntity.isUniqueDisplayName,
avatarUrl = timelineEventEntity.senderAvatar
),
readReceipts = readReceipts readReceipts = readReceipts
?.distinctBy { ?.distinctBy {
it.user it.user

View File

@ -202,7 +202,7 @@ internal class LocalEchoEventFactory @Inject constructor(
permalink, permalink,
stringProvider.getString(R.string.message_reply_to_prefix), stringProvider.getString(R.string.message_reply_to_prefix),
userLink, userLink,
originalEvent.getDisambiguatedDisplayName(), originalEvent.senderInfo.getDisambiguatedDisplayName(),
body.takeFormatted(), body.takeFormatted(),
createTextContent(newBodyText, newBodyAutoMarkdown).takeFormatted() createTextContent(newBodyText, newBodyAutoMarkdown).takeFormatted()
) )

View File

@ -20,9 +20,9 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.session.events.model.toModel import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageContent import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent import im.vector.matrix.android.api.session.room.model.message.MessageWithAttachmentContent
import im.vector.matrix.android.api.session.room.sender.SenderInfo
import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult
import im.vector.matrix.android.api.session.room.uploads.UploadEvent import im.vector.matrix.android.api.session.room.uploads.UploadEvent
import im.vector.matrix.android.api.session.room.uploads.UploadSenderInfo
import im.vector.matrix.android.internal.network.executeRequest import im.vector.matrix.android.internal.network.executeRequest
import im.vector.matrix.android.internal.session.filter.FilterFactory import im.vector.matrix.android.internal.session.filter.FilterFactory
import im.vector.matrix.android.internal.session.room.RoomAPI import im.vector.matrix.android.internal.session.room.RoomAPI
@ -60,7 +60,7 @@ internal class DefaultGetUploadsTask @Inject constructor(
var uploadEvents = listOf<UploadEvent>() var uploadEvents = listOf<UploadEvent>()
val cacheOfSenderInfos = mutableMapOf<String, UploadSenderInfo>() val cacheOfSenderInfos = mutableMapOf<String, SenderInfo>()
// Get a snapshot of all room members // Get a snapshot of all room members
monarchy.doWithRealm { realm -> monarchy.doWithRealm { realm ->
@ -74,11 +74,11 @@ internal class DefaultGetUploadsTask @Inject constructor(
val senderInfo = cacheOfSenderInfos.getOrPut(senderId) { val senderInfo = cacheOfSenderInfos.getOrPut(senderId) {
val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(senderId) val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(senderId)
UploadSenderInfo( SenderInfo(
senderId = senderId, userId = senderId,
senderName = roomMemberSummaryEntity?.displayName, displayName = roomMemberSummaryEntity?.displayName,
isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName(roomMemberSummaryEntity?.displayName), isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName(roomMemberSummaryEntity?.displayName),
senderAvatar = roomMemberSummaryEntity?.avatarUrl avatarUrl = roomMemberSummaryEntity?.avatarUrl
) )
} }
@ -86,7 +86,7 @@ internal class DefaultGetUploadsTask @Inject constructor(
root = event, root = event,
eventId = eventId, eventId = eventId,
contentWithAttachmentContent = messageWithAttachmentContent, contentWithAttachmentContent = messageWithAttachmentContent,
uploadSenderInfo = senderInfo senderInfo = senderInfo
) )
} }
} }

View File

@ -458,7 +458,7 @@ class RoomDetailFragment @Inject constructor(
autoCompleter.enterSpecialMode() autoCompleter.enterSpecialMode()
// switch to expanded bar // switch to expanded bar
composerLayout.composerRelatedMessageTitle.apply { composerLayout.composerRelatedMessageTitle.apply {
text = event.getDisambiguatedDisplayName() text = event.senderInfo.getDisambiguatedDisplayName()
setTextColor(ContextCompat.getColor(requireContext(), getColorFromUserId(event.root.senderId))) setTextColor(ContextCompat.getColor(requireContext(), getColorFromUserId(event.root.senderId)))
} }
@ -477,11 +477,7 @@ class RoomDetailFragment @Inject constructor(
composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes)) composerLayout.composerRelatedMessageActionIcon.setImageDrawable(ContextCompat.getDrawable(requireContext(), iconRes))
composerLayout.sendButton.contentDescription = getString(descriptionRes) composerLayout.sendButton.contentDescription = getString(descriptionRes)
avatarRenderer.render( avatarRenderer.render(event.senderInfo.toMatrixItem(), composerLayout.composerRelatedMessageAvatar)
MatrixItem.UserItem(event.root.senderId
?: "", event.getDisambiguatedDisplayName(), event.senderAvatar),
composerLayout.composerRelatedMessageAvatar
)
composerLayout.expand { composerLayout.expand {
if (isAdded) { if (isAdded) {

View File

@ -85,12 +85,10 @@ class MergedHeaderItemFactory @Inject constructor(private val avatarRenderer: Av
if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) { if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) {
highlighted = true highlighted = true
} }
val senderAvatar = mergedEvent.senderAvatar
val senderName = mergedEvent.getDisambiguatedDisplayName()
val data = BasedMergedItem.Data( val data = BasedMergedItem.Data(
userId = mergedEvent.root.senderId ?: "", userId = mergedEvent.root.senderId ?: "",
avatarUrl = senderAvatar, avatarUrl = mergedEvent.senderInfo.avatarUrl,
memberName = senderName, memberName = mergedEvent.senderInfo.getDisambiguatedDisplayName(),
localId = mergedEvent.localId, localId = mergedEvent.localId,
eventId = mergedEvent.root.eventId ?: "" eventId = mergedEvent.root.eventId ?: ""
) )
@ -158,12 +156,10 @@ class MergedHeaderItemFactory @Inject constructor(private val avatarRenderer: Av
if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) { if (!highlighted && mergedEvent.root.eventId == eventIdToHighlight) {
highlighted = true highlighted = true
} }
val senderAvatar = mergedEvent.senderAvatar
val senderName = mergedEvent.getDisambiguatedDisplayName()
val data = BasedMergedItem.Data( val data = BasedMergedItem.Data(
userId = mergedEvent.root.senderId ?: "", userId = mergedEvent.root.senderId ?: "",
avatarUrl = senderAvatar, avatarUrl = mergedEvent.senderInfo.avatarUrl,
memberName = senderName, memberName = mergedEvent.senderInfo.getDisambiguatedDisplayName(),
localId = mergedEvent.localId, localId = mergedEvent.localId,
eventId = mergedEvent.root.eventId ?: "" eventId = mergedEvent.root.eventId ?: ""
) )

View File

@ -45,7 +45,7 @@ class DisplayableEventFormatter @Inject constructor(
return stringProvider.getString(R.string.encrypted_message) return stringProvider.getString(R.string.encrypted_message)
} }
val senderName = timelineEvent.getDisambiguatedDisplayName() val senderName = timelineEvent.senderInfo.getDisambiguatedDisplayName()
when (timelineEvent.root.getClearType()) { when (timelineEvent.root.getClearType()) {
EventType.MESSAGE -> { EventType.MESSAGE -> {

View File

@ -47,20 +47,20 @@ class NoticeEventFormatter @Inject constructor(private val sessionHolder: Active
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_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_JOIN_RULES -> formatJoinRulesEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName())
EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root) EventType.STATE_ROOM_CREATE -> formatRoomCreateEvent(timelineEvent.root)
EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_NAME -> formatRoomNameEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName())
EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_TOPIC -> formatRoomTopicEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName())
EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_MEMBER -> formatRoomMemberEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName())
EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_ALIASES -> formatRoomAliasesEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName())
EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_CANONICAL_ALIAS -> formatRoomCanonicalAliasEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName())
EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_HISTORY_VISIBILITY -> formatRoomHistoryVisibilityEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName())
EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_GUEST_ACCESS -> formatRoomGuestAccessEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName())
EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_ENCRYPTION -> formatRoomEncryptionEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName())
EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.getDisambiguatedDisplayName()) EventType.STATE_ROOM_TOMBSTONE -> formatRoomTombstoneEvent(timelineEvent.senderInfo.getDisambiguatedDisplayName())
EventType.CALL_INVITE, EventType.CALL_INVITE,
EventType.CALL_HANGUP, EventType.CALL_HANGUP,
EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.getDisambiguatedDisplayName()) EventType.CALL_ANSWER -> formatCallEvent(timelineEvent.root, timelineEvent.senderInfo.getDisambiguatedDisplayName())
EventType.MESSAGE, EventType.MESSAGE,
EventType.REACTION, EventType.REACTION,
EventType.KEY_VERIFICATION_START, EventType.KEY_VERIFICATION_START,

View File

@ -64,16 +64,14 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
val showInformation = val showInformation =
addDaySeparator addDaySeparator
|| event.senderAvatar != nextEvent?.senderAvatar || event.senderInfo.avatarUrl != nextEvent?.senderInfo?.avatarUrl
|| event.getDisambiguatedDisplayName() != nextEvent?.getDisambiguatedDisplayName() || event.senderInfo.getDisambiguatedDisplayName() != nextEvent?.senderInfo?.getDisambiguatedDisplayName()
|| (nextEvent.root.getClearType() != EventType.MESSAGE && nextEvent.root.getClearType() != EventType.ENCRYPTED) || (nextEvent.root.getClearType() != EventType.MESSAGE && nextEvent.root.getClearType() != EventType.ENCRYPTED)
|| isNextMessageReceivedMoreThanOneHourAgo || isNextMessageReceivedMoreThanOneHourAgo
|| isTileTypeMessage(nextEvent) || isTileTypeMessage(nextEvent)
val time = dateFormatter.formatMessageHour(date) val time = dateFormatter.formatMessageHour(date)
val avatarUrl = event.senderAvatar val formattedMemberName = span(event.senderInfo.getDisambiguatedDisplayName()) {
val memberName = event.getDisambiguatedDisplayName()
val formattedMemberName = span(memberName) {
textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId)) textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId))
} }
@ -85,7 +83,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
sendState = event.root.sendState, sendState = event.root.sendState,
time = time, time = time,
ageLocalTS = event.root.ageLocalTs, ageLocalTS = event.root.ageLocalTs,
avatarUrl = avatarUrl, avatarUrl = event.senderInfo.avatarUrl,
memberName = formattedMemberName, memberName = formattedMemberName,
showInformation = showInformation, showInformation = showInformation,
orderedReactionList = event.annotations?.reactionsSummary orderedReactionList = event.annotations?.reactionsSummary

View File

@ -111,7 +111,7 @@ class ViewReactionsViewModel @AssistedInject constructor(@Assisted
event.root.eventId!!, event.root.eventId!!,
summary.key, summary.key,
event.root.senderId ?: "", event.root.senderId ?: "",
event.getDisambiguatedDisplayName(), event.senderInfo.getDisambiguatedDisplayName(),
dateFormatter.formatRelativeDateTime(event.root.originServerTs) dateFormatter.formatRelativeDateTime(event.root.originServerTs)
) )

View File

@ -93,7 +93,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
// Ok room is not known in store, but we can still display something // Ok room is not known in store, but we can still display something
val body = displayableEventFormatter.format(event, false) val body = displayableEventFormatter.format(event, false)
val roomName = stringProvider.getString(R.string.notification_unknown_room_name) val roomName = stringProvider.getString(R.string.notification_unknown_room_name)
val senderDisplayName = event.getDisambiguatedDisplayName() val senderDisplayName = event.senderInfo.getDisambiguatedDisplayName()
val notifiableEvent = NotifiableMessageEvent( val notifiableEvent = NotifiableMessageEvent(
eventId = event.root.eventId!!, eventId = event.root.eventId!!,
@ -126,7 +126,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
val body = displayableEventFormatter.format(event, false).toString() val body = displayableEventFormatter.format(event, false).toString()
val roomName = room.roomSummary()?.displayName ?: "" val roomName = room.roomSummary()?.displayName ?: ""
val senderDisplayName = event.getDisambiguatedDisplayName() val senderDisplayName = event.senderInfo.getDisambiguatedDisplayName()
val notifiableEvent = NotifiableMessageEvent( val notifiableEvent = NotifiableMessageEvent(
eventId = event.root.eventId!!, eventId = event.root.eventId!!,
@ -151,7 +151,7 @@ class NotifiableEventResolver @Inject constructor(private val stringProvider: St
ContentUrlResolver.ThumbnailMethod.SCALE) ContentUrlResolver.ThumbnailMethod.SCALE)
notifiableEvent.senderAvatarPath = session.contentUrlResolver() notifiableEvent.senderAvatarPath = session.contentUrlResolver()
.resolveThumbnail(event.senderAvatar, .resolveThumbnail(event.senderInfo.avatarUrl,
250, 250,
250, 250,
ContentUrlResolver.ThumbnailMethod.SCALE) ContentUrlResolver.ThumbnailMethod.SCALE)

View File

@ -109,7 +109,7 @@ class UploadsFileController @Inject constructor(
id(uploadEvent.eventId) id(uploadEvent.eventId)
title(uploadEvent.contentWithAttachmentContent.body) title(uploadEvent.contentWithAttachmentContent.body)
subtitle(stringProvider.getString(R.string.uploads_files_subtitle, subtitle(stringProvider.getString(R.string.uploads_files_subtitle,
uploadEvent.uploadSenderInfo.getDisambiguatedDisplayName(), uploadEvent.senderInfo.getDisambiguatedDisplayName(),
dateFormatter.formatRelativeDateTime(uploadEvent.root.originServerTs))) dateFormatter.formatRelativeDateTime(uploadEvent.root.originServerTs)))
listener(object : UploadsFileItem.Listener { listener(object : UploadsFileItem.Listener {
override fun onItemClicked() { override fun onItemClicked() {