Merge pull request #109 from vector-im/feature/timeline_formatting

This commit is contained in:
ganfra
2019-05-06 15:01:08 +02:00
committed by GitHub
59 changed files with 1444 additions and 350 deletions

View File

@ -20,7 +20,7 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.InstrumentedTest
import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimeline
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
import im.vector.matrix.android.internal.session.room.timeline.TokenChunkEventPersistor
@ -57,7 +57,7 @@ internal class TimelineTest : InstrumentedTest {
val tokenChunkEventPersistor = TokenChunkEventPersistor(monarchy)
val paginationTask = FakePaginationTask(tokenChunkEventPersistor)
val getContextOfEventTask = FakeGetContextOfEventTask(tokenChunkEventPersistor)
val roomMemberExtractor = RoomMemberExtractor(ROOM_ID)
val roomMemberExtractor = SenderRoomMemberExtractor(ROOM_ID)
val timelineEventFactory = TimelineEventFactory(roomMemberExtractor)
return DefaultTimeline(ROOM_ID, initialEventId, monarchy.realmConfiguration, taskExecutor, getContextOfEventTask, timelineEventFactory, paginationTask, null)
}

View File

@ -27,8 +27,8 @@ data class ContentAttachmentData(
val height: Long? = 0,
val width: Long? = 0,
val name: String? = null,
val path: String? = null,
val mimeType: String? = null,
val path: String,
val mimeType: String,
val type: Type
) : Parcelable {

View File

@ -18,9 +18,15 @@ package im.vector.matrix.android.api.session.content
interface ContentUploadStateTracker {
fun track(eventId: String, updateListener: UpdateListener)
fun track(key: String, updateListener: UpdateListener)
fun untrack(eventId: String, updateListener: UpdateListener)
fun untrack(key: String, updateListener: UpdateListener)
fun setFailure(key: String)
fun setSuccess(key: String)
fun setProgress(key: String, current: Long, total: Long)
interface UpdateListener {
fun onUpdate(state: State)

View File

@ -29,7 +29,8 @@ data class TimelineEvent(
val root: Event,
val localId: String,
val displayIndex: Int,
val roomMember: RoomMember?,
val senderName: String?,
val senderAvatar: String?,
val sendState: SendState
) {

View File

@ -56,14 +56,10 @@ internal fun ChunkEntity.merge(roomId: String,
if (direction == PaginationDirection.FORWARDS) {
this.nextToken = chunkToMerge.nextToken
this.isLastForward = chunkToMerge.isLastForward
this.forwardsStateIndex = chunkToMerge.forwardsStateIndex
this.forwardsDisplayIndex = chunkToMerge.forwardsDisplayIndex
eventsToMerge = chunkToMerge.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.ASCENDING)
} else {
this.prevToken = chunkToMerge.prevToken
this.isLastBackward = chunkToMerge.isLastBackward
this.backwardsStateIndex = chunkToMerge.backwardsStateIndex
this.backwardsDisplayIndex = chunkToMerge.backwardsDisplayIndex
eventsToMerge = chunkToMerge.events.sort(EventEntityFields.DISPLAY_INDEX, Sort.DESCENDING)
}
eventsToMerge.forEach {
@ -119,20 +115,20 @@ internal fun ChunkEntity.add(roomId: String,
this.displayIndex = currentDisplayIndex
this.sendState = SendState.SYNCED
}
// We are not using the order of the list, but will be sorting with displayIndex field
events.add(eventEntity)
val position = if (direction == PaginationDirection.FORWARDS) 0 else this.events.size
events.add(position, eventEntity)
}
internal fun ChunkEntity.lastDisplayIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
return when (direction) {
PaginationDirection.FORWARDS -> forwardsDisplayIndex
PaginationDirection.BACKWARDS -> backwardsDisplayIndex
} ?: defaultValue
PaginationDirection.FORWARDS -> forwardsDisplayIndex
PaginationDirection.BACKWARDS -> backwardsDisplayIndex
} ?: defaultValue
}
internal fun ChunkEntity.lastStateIndex(direction: PaginationDirection, defaultValue: Int = 0): Int {
return when (direction) {
PaginationDirection.FORWARDS -> forwardsStateIndex
PaginationDirection.BACKWARDS -> backwardsStateIndex
} ?: defaultValue
PaginationDirection.FORWARDS -> forwardsStateIndex
PaginationDirection.BACKWARDS -> backwardsStateIndex
} ?: defaultValue
}

View File

@ -31,7 +31,7 @@ internal class ContentModule {
}
scope(DefaultSession.SCOPE) {
ContentUploader(get(), get(), get<ContentUploadStateTracker>() as DefaultContentUploadStateTracker)
FileUploader(get(), get())
}
scope(DefaultSession.SCOPE) {

View File

@ -23,42 +23,42 @@ import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
internal class DefaultContentUploadStateTracker : ContentUploadStateTracker {
private val mainHandler = Handler(Looper.getMainLooper())
private val progressByEvent = mutableMapOf<String, ContentUploadStateTracker.State>()
private val listenersByEvent = mutableMapOf<String, MutableList<ContentUploadStateTracker.UpdateListener>>()
private val states = mutableMapOf<String, ContentUploadStateTracker.State>()
private val listeners = mutableMapOf<String, MutableList<ContentUploadStateTracker.UpdateListener>>()
override fun track(eventId: String, updateListener: ContentUploadStateTracker.UpdateListener) {
val listeners = listenersByEvent[eventId] ?: ArrayList()
override fun track(key: String, updateListener: ContentUploadStateTracker.UpdateListener) {
val listeners = listeners[key] ?: ArrayList()
listeners.add(updateListener)
listenersByEvent[eventId] = listeners
val currentState = progressByEvent[eventId] ?: ContentUploadStateTracker.State.Idle
this.listeners[key] = listeners
val currentState = states[key] ?: ContentUploadStateTracker.State.Idle
mainHandler.post { updateListener.onUpdate(currentState) }
}
override fun untrack(eventId: String, updateListener: ContentUploadStateTracker.UpdateListener) {
listenersByEvent[eventId]?.apply {
override fun untrack(key: String, updateListener: ContentUploadStateTracker.UpdateListener) {
listeners[key]?.apply {
remove(updateListener)
}
}
internal fun setFailure(eventId: String) {
override fun setFailure(key: String) {
val failure = ContentUploadStateTracker.State.Failure
updateState(eventId, failure)
updateState(key, failure)
}
internal fun setSuccess(eventId: String) {
override fun setSuccess(key: String) {
val success = ContentUploadStateTracker.State.Success
updateState(eventId, success)
updateState(key, success)
}
internal fun setProgress(eventId: String, current: Long, total: Long) {
override fun setProgress(key: String, current: Long, total: Long) {
val progressData = ContentUploadStateTracker.State.ProgressData(current, total)
updateState(eventId, progressData)
updateState(key, progressData)
}
private fun updateState(eventId: String, state: ContentUploadStateTracker.State) {
progressByEvent[eventId] = state
private fun updateState(key: String, state: ContentUploadStateTracker.State) {
states[key] = state
mainHandler.post {
listenersByEvent[eventId]?.also { listeners ->
listeners[key]?.also { listeners ->
listeners.forEach { it.onUpdate(state) }
}
}

View File

@ -19,7 +19,6 @@ package im.vector.matrix.android.internal.session.content
import arrow.core.Try
import arrow.core.Try.Companion.raise
import im.vector.matrix.android.api.auth.data.SessionParams
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.internal.di.MoshiProvider
import im.vector.matrix.android.internal.network.ProgressRequestBody
import okhttp3.HttpUrl
@ -31,44 +30,51 @@ import java.io.File
import java.io.IOException
internal class ContentUploader(private val okHttpClient: OkHttpClient,
private val sessionParams: SessionParams,
private val contentUploadProgressTracker: DefaultContentUploadStateTracker) {
internal class FileUploader(private val okHttpClient: OkHttpClient,
private val sessionParams: SessionParams) {
private val uploadUrl = sessionParams.homeServerConnectionConfig.homeServerUri.toString() + URI_PREFIX_CONTENT_API + "upload"
private val moshi = MoshiProvider.providesMoshi()
private val responseAdapter = moshi.adapter(ContentUploadResponse::class.java)
fun uploadFile(eventId: String, attachment: ContentAttachmentData): Try<ContentUploadResponse> {
if (attachment.path == null || attachment.mimeType == null) {
return raise(RuntimeException())
}
val file = File(attachment.path)
val urlString = sessionParams.homeServerConnectionConfig.homeServerUri.toString() + URI_PREFIX_CONTENT_API + "upload"
val urlBuilder = HttpUrl.parse(urlString)?.newBuilder()
?: return raise(RuntimeException())
fun uploadFile(file: File,
filename: String?,
mimeType: String,
progressListener: ProgressRequestBody.Listener? = null): Try<ContentUploadResponse> {
val uploadBody = RequestBody.create(MediaType.parse(mimeType), file)
return upload(uploadBody, filename, progressListener)
}
fun uploadByteArray(byteArray: ByteArray,
filename: String?,
mimeType: String,
progressListener: ProgressRequestBody.Listener? = null): Try<ContentUploadResponse> {
val uploadBody = RequestBody.create(MediaType.parse(mimeType), byteArray)
return upload(uploadBody, filename, progressListener)
}
private fun upload(uploadBody: RequestBody, filename: String?, progressListener: ProgressRequestBody.Listener?): Try<ContentUploadResponse> {
val urlBuilder = HttpUrl.parse(uploadUrl)?.newBuilder() ?: return raise(RuntimeException())
val httpUrl = urlBuilder
.addQueryParameter(
"filename", attachment.name
).build()
.addQueryParameter("filename", filename)
.build()
val requestBody = RequestBody.create(
MediaType.parse(attachment.mimeType),
file
)
val progressRequestBody = ProgressRequestBody(requestBody, object : ProgressRequestBody.Listener {
override fun onProgress(current: Long, total: Long) {
contentUploadProgressTracker.setProgress(eventId, current, total)
}
})
val requestBody = if (progressListener != null) ProgressRequestBody(uploadBody, progressListener) else uploadBody
val request = Request.Builder()
.url(httpUrl)
.post(progressRequestBody)
.post(requestBody)
.build()
val result = Try {
return Try {
okHttpClient.newCall(request).execute().use { response ->
if (!response.isSuccessful) {
throw IOException()
@ -80,11 +86,7 @@ internal class ContentUploader(private val okHttpClient: OkHttpClient,
}
}
}
if (result.isFailure()) {
contentUploadProgressTracker.setFailure(eventId)
} else {
contentUploadProgressTracker.setSuccess(eventId)
}
return result
}
}

View File

@ -0,0 +1,68 @@
/*
* Copyright 2019 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.internal.session.content
import android.graphics.Bitmap
import android.media.ThumbnailUtils
import android.provider.MediaStore
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import java.io.ByteArrayOutputStream
import java.io.File
internal object ThumbnailExtractor {
class ThumbnailData(
val width: Int,
val height: Int,
val size: Long,
val bytes: ByteArray,
val mimeType: String
)
fun extractThumbnail(attachment: ContentAttachmentData): ThumbnailData? {
val file = File(attachment.path)
if (!file.exists() || !file.isFile) {
return null
}
return if (attachment.type == ContentAttachmentData.Type.VIDEO) {
extractVideoThumbnail(attachment)
} else {
null
}
}
private fun extractVideoThumbnail(attachment: ContentAttachmentData): ThumbnailData? {
val thumbnail = ThumbnailUtils.createVideoThumbnail(attachment.path, MediaStore.Video.Thumbnails.MINI_KIND)
val outputStream = ByteArrayOutputStream()
thumbnail.compress(Bitmap.CompressFormat.JPEG, 100, outputStream)
val thumbnailWidth = thumbnail.width
val thumbnailHeight = thumbnail.height
val thumbnailSize = outputStream.size()
val thumbnailData = ThumbnailData(
width = thumbnailWidth,
height = thumbnailHeight,
size = thumbnailSize.toLong(),
bytes = outputStream.toByteArray(),
mimeType = "image/jpeg"
)
thumbnail.recycle()
outputStream.reset()
return thumbnailData
}
}

View File

@ -21,20 +21,29 @@ import androidx.work.CoroutineWorker
import androidx.work.WorkerParameters
import com.squareup.moshi.JsonClass
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.content.ContentUploadStateTracker
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.events.model.toContent
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent
import im.vector.matrix.android.api.session.room.model.message.MessageImageContent
import im.vector.matrix.android.api.session.room.model.message.MessageVideoContent
import im.vector.matrix.android.internal.di.MatrixKoinComponent
import im.vector.matrix.android.internal.network.ProgressRequestBody
import im.vector.matrix.android.internal.session.room.send.SendEventWorker
import im.vector.matrix.android.internal.util.WorkerParamsFactory
import org.koin.standalone.inject
import timber.log.Timber
import java.io.File
internal class UploadContentWorker(context: Context, params: WorkerParameters)
: CoroutineWorker(context, params), MatrixKoinComponent {
private val mediaUploader by inject<ContentUploader>()
private val fileUploader by inject<FileUploader>()
private val contentUploadProgressTracker by inject<ContentUploadStateTracker>()
@JsonClass(generateAdapter = true)
internal data class Params(
@ -47,28 +56,65 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters)
val params = WorkerParamsFactory.fromData<Params>(inputData)
?: return Result.failure()
if (params.event.eventId == null) {
return Result.failure()
val eventId = params.event.eventId ?: return Result.failure()
val attachment = params.attachment
val thumbnailData = ThumbnailExtractor.extractThumbnail(params.attachment)
val attachmentFile = createAttachmentFile(attachment) ?: return Result.failure()
var uploadedThumbnailUrl: String? = null
if (thumbnailData != null) {
fileUploader
.uploadByteArray(thumbnailData.bytes, "thumb_${attachment.name}", thumbnailData.mimeType)
.fold(
{ Timber.e(it) },
{ uploadedThumbnailUrl = it.contentUri }
)
}
return mediaUploader
.uploadFile(params.event.eventId, params.attachment)
.fold({ handleFailure() }, { handleSuccess(params, it) })
val progressListener = object : ProgressRequestBody.Listener {
override fun onProgress(current: Long, total: Long) {
contentUploadProgressTracker.setProgress(eventId, current, total)
}
}
return fileUploader
.uploadFile(attachmentFile, attachment.name, attachment.mimeType, progressListener)
.fold(
{ handleFailure(params) },
{ handleSuccess(params, it.contentUri, uploadedThumbnailUrl) }
)
}
private fun handleFailure(): Result {
return Result.retry()
private fun createAttachmentFile(attachment: ContentAttachmentData): File? {
return try {
File(attachment.path)
} catch (e: Exception) {
Timber.e(e)
null
}
}
private fun handleSuccess(params: Params, contentUploadResponse: ContentUploadResponse): Result {
val event = updateEvent(params.event, contentUploadResponse.contentUri)
private fun handleFailure(params: Params): Result {
contentUploadProgressTracker.setFailure(params.event.eventId!!)
return Result.failure()
}
private fun handleSuccess(params: Params,
attachmentUrl: String,
thumbnailUrl: String?): Result {
contentUploadProgressTracker.setFailure(params.event.eventId!!)
val event = updateEvent(params.event, attachmentUrl, thumbnailUrl)
val sendParams = SendEventWorker.Params(params.roomId, event)
return Result.success(WorkerParamsFactory.toData(sendParams))
}
private fun updateEvent(event: Event, url: String): Event {
private fun updateEvent(event: Event, url: String, thumbnailUrl: String? = null): Event {
val messageContent: MessageContent = event.content.toModel() ?: return event
val updatedContent = when (messageContent) {
is MessageImageContent -> messageContent.update(url)
is MessageVideoContent -> messageContent.update(url, thumbnailUrl)
is MessageFileContent -> messageContent.update(url)
is MessageAudioContent -> messageContent.update(url)
else -> messageContent
}
return event.copy(content = updatedContent.toContent())
@ -78,6 +124,18 @@ internal class UploadContentWorker(context: Context, params: WorkerParameters)
return copy(url = url)
}
private fun MessageVideoContent.update(url: String, thumbnailUrl: String?): MessageVideoContent {
return copy(url = url, info = info?.copy(thumbnailUrl = thumbnailUrl))
}
private fun MessageFileContent.update(url: String): MessageFileContent {
return copy(url = url)
}
private fun MessageAudioContent.update(url: String): MessageAudioContent {
return copy(url = url)
}
}

View File

@ -21,7 +21,7 @@ import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.internal.session.room.invite.InviteTask
import im.vector.matrix.android.internal.session.room.members.DefaultRoomMembersService
import im.vector.matrix.android.internal.session.room.members.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor
import im.vector.matrix.android.internal.session.room.read.DefaultReadService
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.session.room.send.DefaultSendService
@ -45,7 +45,7 @@ internal class RoomFactory(private val loadRoomMembersTask: LoadRoomMembersTask,
private val taskExecutor: TaskExecutor) {
fun instantiate(roomId: String): Room {
val roomMemberExtractor = RoomMemberExtractor(roomId)
val roomMemberExtractor = SenderRoomMemberExtractor(roomId)
val timelineEventFactory = TimelineEventFactory(roomMemberExtractor)
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, contextOfEventTask, timelineEventFactory, paginationTask)
val sendService = DefaultSendService(roomId, eventFactory, monarchy)

View File

@ -48,6 +48,18 @@ internal class RoomMembers(private val realm: Realm,
}
}
fun isUniqueDisplayName(displayName: String?): Boolean {
if(displayName.isNullOrEmpty()){
return true
}
return EventEntity
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)
.contains(EventEntityFields.CONTENT, displayName)
.distinct(EventEntityFields.STATE_KEY)
.findAll()
.size == 1
}
fun queryRoomMembersEvent(): RealmQuery<EventEntity> {
return EventEntity
.where(realm, roomId, EventType.STATE_ROOM_MEMBER)

View File

@ -20,48 +20,45 @@ import im.vector.matrix.android.api.session.events.model.EventType
import im.vector.matrix.android.api.session.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.internal.database.mapper.ContentMapper
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.findIncludingEvent
import im.vector.matrix.android.internal.database.query.next
import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmQuery
internal class RoomMemberExtractor(private val roomId: String) {
private val cached = HashMap<String, RoomMember?>()
internal class SenderRoomMemberExtractor(private val roomId: String) {
fun extractFrom(event: EventEntity): RoomMember? {
val sender = event.sender ?: return null
val cacheKey = sender + event.stateIndex
if (cached.containsKey(cacheKey)) {
return cached[cacheKey]
}
// If the event is unlinked we want to fetch unlinked state events
val unlinked = event.isUnlinked
// When stateIndex is negative, we try to get the next stateEvent prevContent()
// If prevContent is null we fallback to the Int.MIN state events content()
val content = if (event.stateIndex <= 0) {
baseQuery(event.realm, roomId, sender, unlinked).next(from = event.stateIndex)?.prevContent
?: baseQuery(event.realm, roomId, sender, unlinked).prev(since = event.stateIndex)?.content
} else {
baseQuery(event.realm, roomId, sender, unlinked).prev(since = event.stateIndex)?.content
val roomEntity = RoomEntity.where(event.realm, roomId = roomId).findFirst() ?: return null
val chunkEntity = ChunkEntity.findIncludingEvent(event.realm, event.eventId)
val content = when {
chunkEntity == null -> null
event.stateIndex <= 0 -> baseQuery(chunkEntity.events, sender, unlinked).next(from = event.stateIndex)?.prevContent
else -> baseQuery(chunkEntity.events, sender, unlinked).prev(since = event.stateIndex)?.content
}
val roomMember: RoomMember? = ContentMapper.map(content).toModel()
cached[cacheKey] = roomMember
return roomMember
val fallbackContent = content
?: baseQuery(roomEntity.untimelinedStateEvents, sender, unlinked).prev(since = event.stateIndex)?.content
return ContentMapper.map(fallbackContent).toModel()
}
private fun baseQuery(realm: Realm,
roomId: String,
private fun baseQuery(list: RealmList<EventEntity>,
sender: String,
isUnlinked: Boolean): RealmQuery<EventEntity> {
val filterMode = if (isUnlinked) EventEntity.LinkFilterMode.UNLINKED_ONLY else EventEntity.LinkFilterMode.LINKED_ONLY
return EventEntity
.where(realm, roomId = roomId, type = EventType.STATE_ROOM_MEMBER, linkFilterMode = filterMode)
return list
.where()
.equalTo(EventEntityFields.STATE_KEY, sender)
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
.equalTo(EventEntityFields.IS_UNLINKED, isUnlinked)
}
}

View File

@ -16,6 +16,7 @@
package im.vector.matrix.android.internal.session.room.send
import android.media.MediaMetadataRetriever
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.content.ContentAttachmentData
import im.vector.matrix.android.api.session.events.model.Event
@ -30,7 +31,9 @@ import im.vector.matrix.android.api.session.room.model.message.MessageImageConte
import im.vector.matrix.android.api.session.room.model.message.MessageTextContent
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.ThumbnailInfo
import im.vector.matrix.android.api.session.room.model.message.VideoInfo
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
internal class LocalEchoEventFactory(private val credentials: Credentials) {
@ -53,7 +56,7 @@ internal class LocalEchoEventFactory(private val credentials: Credentials) {
type = MessageType.MSGTYPE_IMAGE,
body = attachment.name ?: "image",
info = ImageInfo(
mimeType = attachment.mimeType ?: "image/png",
mimeType = attachment.mimeType,
width = attachment.width?.toInt() ?: 0,
height = attachment.height?.toInt() ?: 0,
size = attachment.size.toInt()
@ -64,15 +67,35 @@ internal class LocalEchoEventFactory(private val credentials: Credentials) {
}
private fun createVideoEvent(roomId: String, attachment: ContentAttachmentData): Event {
val mediaDataRetriever = MediaMetadataRetriever()
mediaDataRetriever.setDataSource(attachment.path)
// Use frame to calculate height and width as we are sure to get the right ones
val firstFrame = mediaDataRetriever.frameAtTime
val height = firstFrame.height
val width = firstFrame.width
mediaDataRetriever.release()
val thumbnailInfo = ThumbnailExtractor.extractThumbnail(attachment)?.let {
ThumbnailInfo(
width = it.width,
height = it.height,
size = it.size,
mimeType = it.mimeType
)
}
val content = MessageVideoContent(
type = MessageType.MSGTYPE_VIDEO,
body = attachment.name ?: "video",
info = VideoInfo(
mimeType = attachment.mimeType ?: "video/mpeg",
width = attachment.width?.toInt() ?: 0,
height = attachment.height?.toInt() ?: 0,
mimeType = attachment.mimeType,
width = width,
height = height,
size = attachment.size,
duration = attachment.duration?.toInt() ?: 0
duration = attachment.duration?.toInt() ?: 0,
// Glide will be able to use the local path and extract a thumbnail.
thumbnailUrl = attachment.path,
thumbnailInfo = thumbnailInfo
),
url = attachment.path
)

View File

@ -27,14 +27,24 @@ import im.vector.matrix.android.api.session.room.timeline.Timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.CancelableBag
import im.vector.matrix.android.api.util.addTo
import im.vector.matrix.android.internal.database.model.*
import im.vector.matrix.android.internal.database.model.ChunkEntity
import im.vector.matrix.android.internal.database.model.ChunkEntityFields
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.findIncludingEvent
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
import im.vector.matrix.android.internal.util.Debouncer
import io.realm.*
import io.realm.OrderedCollectionChangeSet
import io.realm.OrderedRealmCollectionChangeListener
import io.realm.Realm
import io.realm.RealmConfiguration
import io.realm.RealmQuery
import io.realm.RealmResults
import io.realm.Sort
import timber.log.Timber
import java.util.*
import java.util.concurrent.atomic.AtomicBoolean
@ -87,9 +97,16 @@ internal class DefaultTimeline(
private val eventsChangeListener = OrderedRealmCollectionChangeListener<RealmResults<EventEntity>> { _, changeSet ->
if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL) {
if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL ) {
handleInitialLoad()
} else {
// If changeSet has deletion we are having a gap, so we clear everything
if(changeSet.deletionRanges.isNotEmpty()){
prevDisplayIndex = DISPLAY_INDEX_UNKNOWN
nextDisplayIndex = DISPLAY_INDEX_UNKNOWN
builtEvents.clear()
timelineEventFactory.clear()
}
changeSet.insertionRanges.forEach { range ->
val (startDisplayIndex, direction) = if (range.startIndex == 0) {
Pair(liveEvents[range.length - 1]!!.displayIndex, Timeline.Direction.FORWARDS)
@ -108,6 +125,7 @@ internal class DefaultTimeline(
buildTimelineEvents(startDisplayIndex, direction, range.length.toLong())
postSnapshot()
}
}
}
}
@ -298,9 +316,9 @@ internal class DefaultTimeline(
private fun executePaginationTask(direction: Timeline.Direction, limit: Int) {
val token = getTokenLive(direction) ?: return
val params = PaginationTask.Params(roomId = roomId,
from = token,
direction = direction.toPaginationDirection(),
limit = limit)
from = token,
direction = direction.toPaginationDirection(),
limit = limit)
Timber.v("Should fetch $limit items $direction")
paginationTask.configureWith(params)

View File

@ -19,19 +19,36 @@ package im.vector.matrix.android.internal.session.room.timeline
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity
import im.vector.matrix.android.internal.session.room.members.RoomMemberExtractor
import im.vector.matrix.android.internal.session.room.members.SenderRoomMemberExtractor
internal class TimelineEventFactory(private val roomMemberExtractor: RoomMemberExtractor) {
internal class TimelineEventFactory(private val roomMemberExtractor: SenderRoomMemberExtractor) {
private val cached = mutableMapOf<String, SenderData>()
fun create(eventEntity: EventEntity): TimelineEvent {
val roomMember = roomMemberExtractor.extractFrom(eventEntity)
val sender = eventEntity.sender
val cacheKey = sender + eventEntity.stateIndex
val senderData = cached.getOrPut(cacheKey) {
val senderRoomMember = roomMemberExtractor.extractFrom(eventEntity)
SenderData(senderRoomMember?.displayName, senderRoomMember?.avatarUrl)
}
return TimelineEvent(
eventEntity.asDomain(),
eventEntity.localId,
eventEntity.displayIndex,
roomMember,
senderData.senderName,
senderData.senderAvatar,
eventEntity.sendState
)
}
fun clear(){
cached.clear()
}
private data class SenderData(
val senderName: String?,
val senderAvatar: String?
)
}