Uploads: rework: provide information about the sender

This commit is contained in:
Benoit Marty 2020-05-20 11:56:54 +02:00
parent 907a786b1a
commit f3a5fb7fe3
12 changed files with 170 additions and 82 deletions

View file

@ -228,12 +228,3 @@ fun Event.isVideoMessage(): Boolean {
else -> false
}
}
fun Event.isPreviewableMessage(): Boolean {
return getClearType() == EventType.MESSAGE
&& when (getClearContent()?.toModel<MessageContent>()?.msgType) {
MessageType.MSGTYPE_IMAGE,
MessageType.MSGTYPE_VIDEO -> true
else -> false
}
}

View file

@ -16,13 +16,12 @@
package im.vector.matrix.android.api.session.room.uploads
import im.vector.matrix.android.api.session.events.model.Event
data class GetUploadsResult(
// List of fetched Events, most recent first
val events: List<Event>,
val events: List<UploadEvent>,
// token to get more events
val nextToken: String,
// True if there are more event to load
val hasMore: Boolean
)

View file

@ -0,0 +1,31 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
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.room.model.message.MessageWithAttachmentContent
/**
* Wrapper around on Event.
* Similar to [im.vector.matrix.android.api.session.room.timeline.TimelineEvent], contains an Event with extra useful data
*/
data class UploadEvent(
val root: Event,
val eventId: String,
val contentWithAttachmentContent: MessageWithAttachmentContent,
val uploadSenderInfo: UploadSenderInfo
)

View file

@ -0,0 +1,33 @@
/*
* Copyright (c) 2020 New Vector Ltd
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package im.vector.matrix.android.api.session.room.uploads
// TODO Maybe use this model for TimelineEvent as well
data class UploadSenderInfo(
val senderId: String,
val senderName: String?,
val isUniqueDisplayName: Boolean,
val senderAvatar: String?
) {
fun getDisambiguatedDisplayName(): String {
return when {
senderName.isNullOrBlank() -> senderId
isUniqueDisplayName -> senderName
else -> "$senderName (${senderId})"
}
}
}

View file

@ -126,6 +126,7 @@ internal class RoomDisplayNameResolver @Inject constructor(private val context:
return name ?: roomId
}
/** See [im.vector.matrix.android.api.session.room.timeline.TimelineEvent.getDisambiguatedDisplayName] */
private fun resolveRoomMemberName(roomMemberSummary: RoomMemberSummaryEntity?,
roomMemberHelper: RoomMemberHelper): String? {
if (roomMemberSummary == null) return null

View file

@ -16,10 +16,17 @@
package im.vector.matrix.android.internal.session.room.uploads
import com.zhuinden.monarchy.Monarchy
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.MessageWithAttachmentContent
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.UploadSenderInfo
import im.vector.matrix.android.internal.network.executeRequest
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.membership.RoomMemberHelper
import im.vector.matrix.android.internal.session.room.timeline.PaginationDirection
import im.vector.matrix.android.internal.session.room.timeline.PaginationResponse
import im.vector.matrix.android.internal.session.sync.SyncTokenStore
@ -39,6 +46,7 @@ internal interface GetUploadsTask : Task<GetUploadsTask.Params, GetUploadsResult
internal class DefaultGetUploadsTask @Inject constructor(
private val roomAPI: RoomAPI,
private val tokenStore: SyncTokenStore,
private val monarchy: Monarchy,
private val eventBus: EventBus)
: GetUploadsTask {
@ -50,8 +58,42 @@ internal class DefaultGetUploadsTask @Inject constructor(
apiCall = roomAPI.getRoomMessagesFrom(params.roomId, since, PaginationDirection.BACKWARDS.value, params.numberOfEvents, filter)
}
var uploadEvents = listOf<UploadEvent>()
val cacheOfSenderInfos = mutableMapOf<String, UploadSenderInfo>()
// Get a snapshot of all room members
monarchy.doWithRealm { realm ->
val roomMemberHelper = RoomMemberHelper(realm, params.roomId)
uploadEvents = chunk.events.mapNotNull { event ->
val eventId = event.eventId ?: return@mapNotNull null
val messageContent = event.getClearContent()?.toModel<MessageContent>() ?: return@mapNotNull null
val messageWithAttachmentContent = (messageContent as? MessageWithAttachmentContent) ?: return@mapNotNull null
val senderId = event.senderId ?: return@mapNotNull null
val senderInfo = cacheOfSenderInfos.getOrPut(senderId) {
val roomMemberSummaryEntity = roomMemberHelper.getLastRoomMember(senderId)
UploadSenderInfo(
senderId = senderId,
senderName = roomMemberSummaryEntity?.displayName,
isUniqueDisplayName = roomMemberHelper.isUniqueDisplayName(roomMemberSummaryEntity?.displayName),
senderAvatar = roomMemberSummaryEntity?.avatarUrl
)
}
UploadEvent(
root = event,
eventId = eventId,
contentWithAttachmentContent = messageWithAttachmentContent,
uploadSenderInfo = senderInfo
)
}
}
return GetUploadsResult(
events = chunk.events,
events = uploadEvents,
nextToken = chunk.end ?: "",
hasMore = chunk.hasMore()
)

View file

@ -16,13 +16,12 @@
package im.vector.riotx.features.roomprofile.uploads
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.uploads.UploadEvent
import im.vector.riotx.core.platform.VectorViewModelAction
sealed class RoomUploadsAction : VectorViewModelAction {
data class Download(val event: Event, val messageContent: MessageWithAttachmentContent) : RoomUploadsAction()
data class Share(val event: Event, val messageContent: MessageWithAttachmentContent) : RoomUploadsAction()
data class Download(val uploadEvent: UploadEvent) : RoomUploadsAction()
data class Share(val uploadEvent: UploadEvent) : RoomUploadsAction()
object Retry : RoomUploadsAction()
object LoadMore : RoomUploadsAction()

View file

@ -27,11 +27,8 @@ import com.airbnb.mvrx.ViewModelContext
import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.isPreviewableMessage
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.file.FileService
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
import im.vector.matrix.android.api.session.room.uploads.GetUploadsResult
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
@ -100,8 +97,10 @@ class RoomUploadsViewModel @AssistedInject constructor(
token = result.nextToken
val groupedEvents = result.events
.filter { it.getClearType() == EventType.MESSAGE && it.getClearContent()?.toModel<MessageContent>() != null }
.groupBy { it.isPreviewableMessage() }
.groupBy {
it.contentWithAttachmentContent.msgType == MessageType.MSGTYPE_IMAGE
|| it.contentWithAttachmentContent.msgType == MessageType.MSGTYPE_VIDEO
}
setState {
copy(
@ -139,10 +138,10 @@ class RoomUploadsViewModel @AssistedInject constructor(
val file = awaitCallback<File> {
session.downloadFile(
FileService.DownloadMode.FOR_EXTERNAL_SHARE,
action.event.eventId ?: "",
action.messageContent.body,
action.messageContent.getFileUrl(),
action.messageContent.encryptedFileInfo?.toElementToDecrypt(),
action.uploadEvent.eventId,
action.uploadEvent.contentWithAttachmentContent.body,
action.uploadEvent.contentWithAttachmentContent.getFileUrl(),
action.uploadEvent.contentWithAttachmentContent.encryptedFileInfo?.toElementToDecrypt(),
it
)
}
@ -159,14 +158,14 @@ class RoomUploadsViewModel @AssistedInject constructor(
val file = awaitCallback<File> {
session.downloadFile(
FileService.DownloadMode.FOR_EXTERNAL_SHARE,
action.event.eventId ?: "",
action.messageContent.body,
action.messageContent.getFileUrl(),
action.messageContent.encryptedFileInfo?.toElementToDecrypt(),
action.uploadEvent.eventId,
action.uploadEvent.contentWithAttachmentContent.body,
action.uploadEvent.contentWithAttachmentContent.getFileUrl(),
action.uploadEvent.contentWithAttachmentContent.encryptedFileInfo?.toElementToDecrypt(),
it)
}
_viewEvents.post(RoomUploadsViewEvents.FileReadyForSaving(file, action.messageContent.body))
_viewEvents.post(RoomUploadsViewEvents.FileReadyForSaving(file, action.uploadEvent.contentWithAttachmentContent.body))
} catch (failure: Throwable) {
_viewEvents.post(RoomUploadsViewEvents.Failure(failure))
}

View file

@ -19,16 +19,16 @@ package im.vector.riotx.features.roomprofile.uploads
import com.airbnb.mvrx.Async
import com.airbnb.mvrx.MvRxState
import com.airbnb.mvrx.Uninitialized
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.uploads.UploadEvent
import im.vector.riotx.features.roomprofile.RoomProfileArgs
data class RoomUploadsViewState(
val roomId: String = "",
val roomSummary: Async<RoomSummary> = Uninitialized,
// Store cumul of pagination result, grouped by type
val mediaEvents: List<Event> = emptyList(),
val fileEvents: List<Event> = emptyList(),
val mediaEvents: List<UploadEvent> = emptyList(),
val fileEvents: List<UploadEvent> = emptyList(),
// Current pagination request
val asyncEventsRequest: Async<Unit> = Uninitialized,
// True if more result are available server side

View file

@ -20,8 +20,7 @@ import android.os.Bundle
import android.view.View
import com.airbnb.mvrx.parentFragmentViewModel
import com.airbnb.mvrx.withState
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.uploads.UploadEvent
import im.vector.riotx.R
import im.vector.riotx.core.extensions.cleanup
import im.vector.riotx.core.extensions.configureWith
@ -54,8 +53,9 @@ class RoomUploadsFilesFragment @Inject constructor(
controller.listener = null
}
override fun onOpenClicked(event: Event) {
TODO()
override fun onOpenClicked(uploadEvent: UploadEvent) {
// Same action than Share
uploadsViewModel.handle(RoomUploadsAction.Share(uploadEvent))
}
override fun onRetry() {
@ -66,12 +66,12 @@ class RoomUploadsFilesFragment @Inject constructor(
uploadsViewModel.handle(RoomUploadsAction.LoadMore)
}
override fun onDownloadClicked(event: Event, messageWithAttachmentContent: MessageWithAttachmentContent) {
uploadsViewModel.handle(RoomUploadsAction.Download(event, messageWithAttachmentContent))
override fun onDownloadClicked(uploadEvent: UploadEvent) {
uploadsViewModel.handle(RoomUploadsAction.Download(uploadEvent))
}
override fun onShareClicked(event: Event, messageWithAttachmentContent: MessageWithAttachmentContent) {
uploadsViewModel.handle(RoomUploadsAction.Share(event, messageWithAttachmentContent))
override fun onShareClicked(uploadEvent: UploadEvent) {
uploadsViewModel.handle(RoomUploadsAction.Share(uploadEvent))
}
override fun invalidate() = withState(uploadsViewModel) { state ->

View file

@ -21,10 +21,7 @@ import com.airbnb.epoxy.VisibilityState
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import im.vector.matrix.android.api.session.events.model.Event
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.MessageWithAttachmentContent
import im.vector.matrix.android.api.session.room.uploads.UploadEvent
import im.vector.riotx.R
import im.vector.riotx.core.date.VectorDateFormatter
import im.vector.riotx.core.epoxy.errorWithRetryItem
@ -44,9 +41,9 @@ class UploadsFileController @Inject constructor(
interface Listener {
fun onRetry()
fun loadMore()
fun onOpenClicked(event: Event)
fun onDownloadClicked(event: Event, messageWithAttachmentContent: MessageWithAttachmentContent)
fun onShareClicked(event: Event, messageWithAttachmentContent: MessageWithAttachmentContent)
fun onOpenClicked(uploadEvent: UploadEvent)
fun onDownloadClicked(uploadEvent: UploadEvent)
fun onShareClicked(uploadEvent: UploadEvent)
}
var listener: Listener? = null
@ -106,27 +103,25 @@ class UploadsFileController @Inject constructor(
}
}
private fun buildFileItems(fileEvents: List<Event>) {
fileEvents.forEach {
val messageContent = it.getClearContent()?.toModel<MessageContent>() ?: return@forEach
val messageWithAttachmentContent = (messageContent as? MessageWithAttachmentContent) ?: return@forEach
private fun buildFileItems(fileEvents: List<UploadEvent>) {
fileEvents.forEach { uploadEvent ->
uploadsFileItem {
id(it.eventId ?: "")
title(messageWithAttachmentContent.body)
// TODO Resolve user displayName
subtitle(stringProvider.getString(R.string.uploads_files_subtitle, it.senderId, dateFormatter.formatRelativeDateTime(it.originServerTs)))
id(uploadEvent.eventId)
title(uploadEvent.contentWithAttachmentContent.body)
subtitle(stringProvider.getString(R.string.uploads_files_subtitle,
uploadEvent.uploadSenderInfo.getDisambiguatedDisplayName(),
dateFormatter.formatRelativeDateTime(uploadEvent.root.originServerTs)))
listener(object : UploadsFileItem.Listener {
override fun onItemClicked() {
listener?.onOpenClicked(it)
listener?.onOpenClicked(uploadEvent)
}
override fun onDownloadClicked() {
listener?.onDownloadClicked(it, messageWithAttachmentContent)
listener?.onDownloadClicked(uploadEvent)
}
override fun onShareClicked() {
listener?.onShareClicked(it, messageWithAttachmentContent)
listener?.onShareClicked(uploadEvent)
}
})
}

View file

@ -22,13 +22,11 @@ import com.airbnb.epoxy.VisibilityState
import com.airbnb.mvrx.Fail
import com.airbnb.mvrx.Loading
import com.airbnb.mvrx.Success
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.isImageMessage
import im.vector.matrix.android.api.session.events.model.isVideoMessage
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageType
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.api.session.room.model.message.getFileUrl
import im.vector.matrix.android.api.session.room.uploads.UploadEvent
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
import im.vector.riotx.R
import im.vector.riotx.core.epoxy.errorWithRetryItem
@ -115,13 +113,13 @@ class UploadsMediaController @Inject constructor(
}
}
private fun buildMediaItems(mediaEvents: List<Event>) {
mediaEvents.forEach { event ->
when {
event.isImageMessage() -> {
val data = event.toImageContentRendererData() ?: return@forEach
private fun buildMediaItems(mediaEvents: List<UploadEvent>) {
mediaEvents.forEach { uploadEvent ->
when (uploadEvent.contentWithAttachmentContent.msgType) {
MessageType.MSGTYPE_IMAGE -> {
val data = uploadEvent.toImageContentRendererData() ?: return@forEach
uploadsImageItem {
id(event.eventId ?: "")
id(uploadEvent.eventId)
imageContentRenderer(imageContentRenderer)
data(data)
listener(object : UploadsImageItem.Listener {
@ -131,10 +129,10 @@ class UploadsMediaController @Inject constructor(
})
}
}
event.isVideoMessage() -> {
val data = event.toVideoContentRendererData() ?: return@forEach
MessageType.MSGTYPE_VIDEO -> {
val data = uploadEvent.toVideoContentRendererData() ?: return@forEach
uploadsVideoItem {
id(event.eventId ?: "")
id(uploadEvent.eventId)
imageContentRenderer(imageContentRenderer)
data(data)
listener(object : UploadsVideoItem.Listener {
@ -148,11 +146,11 @@ class UploadsMediaController @Inject constructor(
}
}
private fun Event.toImageContentRendererData(): ImageContentRenderer.Data? {
val messageContent = getClearContent()?.toModel<MessageImageContent>() ?: return null
private fun UploadEvent.toImageContentRendererData(): ImageContentRenderer.Data? {
val messageContent = (contentWithAttachmentContent as? MessageImageContent) ?: return null
return ImageContentRenderer.Data(
eventId = eventId ?: "",
eventId = eventId,
filename = messageContent.body,
url = messageContent.getFileUrl(),
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),
@ -163,11 +161,11 @@ class UploadsMediaController @Inject constructor(
)
}
private fun Event.toVideoContentRendererData(): VideoContentRenderer.Data? {
val messageContent = getClearContent()?.toModel<MessageVideoContent>() ?: return null
private fun UploadEvent.toVideoContentRendererData(): VideoContentRenderer.Data? {
val messageContent = (contentWithAttachmentContent as? MessageVideoContent) ?: return null
val thumbnailData = ImageContentRenderer.Data(
eventId = eventId ?: "",
eventId = eventId,
filename = messageContent.body,
url = messageContent.videoInfo?.thumbnailFile?.url ?: messageContent.videoInfo?.thumbnailUrl,
elementToDecrypt = messageContent.videoInfo?.thumbnailFile?.toElementToDecrypt(),
@ -178,7 +176,7 @@ class UploadsMediaController @Inject constructor(
)
return VideoContentRenderer.Data(
eventId = eventId ?: "",
eventId = eventId,
filename = messageContent.body,
url = messageContent.getFileUrl(),
elementToDecrypt = messageContent.encryptedFileInfo?.toElementToDecrypt(),