Last event on room list

This commit is contained in:
Benoit Marty
2019-07-01 12:35:48 +02:00
committed by Benoit Marty
parent 0d433b2620
commit 4d79485fee
20 changed files with 196 additions and 106 deletions

View File

@ -16,8 +16,8 @@
package im.vector.matrix.android.api.session.room.model
import im.vector.matrix.android.api.session.events.model.Event
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
/**
* This class holds some data of a room.
@ -29,7 +29,7 @@ data class RoomSummary(
val topic: String = "",
val avatarUrl: String = "",
val isDirect: Boolean = false,
val lastMessage: Event? = null,
val latestEvent: TimelineEvent? = null,
val otherMemberIds: List<String> = emptyList(),
val notificationCount: Int = 0,
val highlightCount: Int = 0,

View File

@ -19,21 +19,26 @@ package im.vector.matrix.android.internal.database.mapper
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
import javax.inject.Inject
internal object RoomSummaryMapper {
internal class RoomSummaryMapper @Inject constructor(private val timelineEventFactory: TimelineEventFactory) {
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
val tags = roomSummaryEntity.tags.map {
RoomTag(it.tagName, it.tagOrder)
}
val latestEvent = roomSummaryEntity.latestEvent?.let {
timelineEventFactory.create(it)
}
return RoomSummary(
roomId = roomSummaryEntity.roomId,
displayName = roomSummaryEntity.displayName ?: "",
topic = roomSummaryEntity.topic ?: "",
avatarUrl = roomSummaryEntity.avatarUrl ?: "",
isDirect = roomSummaryEntity.isDirect,
lastMessage = roomSummaryEntity.lastMessage?.asDomain(),
latestEvent = latestEvent,
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(),
highlightCount = roomSummaryEntity.highlightCount,
notificationCount = roomSummaryEntity.notificationCount,
@ -42,7 +47,3 @@ internal object RoomSummaryMapper {
)
}
}
internal fun RoomSummaryEntity.asDomain(): RoomSummary {
return RoomSummaryMapper.map(this)
}

View File

@ -27,7 +27,7 @@ internal open class RoomSummaryEntity(@PrimaryKey var roomId: String = "",
var displayName: String? = "",
var avatarUrl: String? = "",
var topic: String? = "",
var lastMessage: EventEntity? = null,
var latestEvent: EventEntity? = null,
var heroes: RealmList<String> = RealmList(),
var joinedMembersCount: Int? = 0,
var invitedMembersCount: Int? = 0,

View File

@ -16,10 +16,12 @@
package im.vector.matrix.android.internal.database.query
import im.vector.matrix.android.internal.database.helper.addSendingEvent
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.EventEntity.LinkFilterMode.*
import im.vector.matrix.android.internal.database.model.EventEntityFields
import im.vector.matrix.android.internal.database.model.RoomEntity
import io.realm.Realm
import io.realm.RealmList
import io.realm.RealmQuery
@ -65,7 +67,14 @@ internal fun EventEntity.Companion.latestEvent(realm: Realm,
roomId: String,
includedTypes: List<String> = emptyList(),
excludedTypes: List<String> = emptyList()): EventEntity? {
val query = ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.events?.where()
val roomEntity = RoomEntity.where(realm, roomId).findFirst() ?: return null
val eventList = if (roomEntity.sendingTimelineEvents.isNotEmpty()) {
roomEntity.sendingTimelineEvents
} else {
ChunkEntity.findLastLiveChunkFromRoom(realm, roomId)?.events
}
val query = eventList?.where()
if (includedTypes.isNotEmpty()) {
query?.`in`(EventEntityFields.TYPE, includedTypes.toTypedArray())
} else if (excludedTypes.isNotEmpty()) {

View File

@ -29,7 +29,7 @@ import im.vector.matrix.android.api.session.room.send.SendService
import im.vector.matrix.android.api.session.room.state.StateService
import im.vector.matrix.android.api.session.room.timeline.TimelineService
import im.vector.matrix.android.internal.database.RealmLiveData
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
import im.vector.matrix.android.internal.database.query.where
@ -38,6 +38,7 @@ import javax.inject.Inject
internal class DefaultRoom @Inject constructor(override val roomId: String,
private val monarchy: Monarchy,
private val roomSummaryMapper: RoomSummaryMapper,
private val timelineService: TimelineService,
private val sendService: SendService,
private val stateService: StateService,
@ -58,7 +59,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
}
Transformations.map(liveRealmData) { results ->
val roomSummaries = results.map { it.asDomain() }
val roomSummaries = results.map { roomSummaryMapper.map(it) }
if (roomSummaries.isEmpty()) {
// Create a dummy RoomSummary to avoid Crash during Sign Out or clear cache
@ -72,7 +73,7 @@ internal class DefaultRoom @Inject constructor(override val roomId: String,
override val roomSummary: RoomSummary?
get() {
var sum: RoomSummaryEntity? = monarchy.fetchCopied { RoomSummaryEntity.where(it, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME).findFirst() }
return sum?.asDomain()
return sum?.let { roomSummaryMapper.map(it) }
}
override fun isEncrypted(): Boolean {

View File

@ -23,12 +23,11 @@ import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.api.session.room.RoomService
import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.create.CreateRoomParams
import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.create.CreateRoomTask
import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith
@ -36,6 +35,7 @@ import im.vector.matrix.android.internal.util.fetchManaged
import javax.inject.Inject
internal class DefaultRoomService @Inject constructor(private val monarchy: Monarchy,
private val roomSummaryMapper: RoomSummaryMapper,
private val createRoomTask: CreateRoomTask,
private val roomFactory: RoomFactory,
private val taskExecutor: TaskExecutor) : RoomService {
@ -55,7 +55,7 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
return monarchy.findAllMappedWithChanges(
{ realm -> RoomSummaryEntity.where(realm).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) },
{ it.asDomain() }
{ roomSummaryMapper.map(it) }
)
}
}

View File

@ -21,6 +21,7 @@ import com.zhuinden.monarchy.Monarchy
import im.vector.matrix.android.api.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.room.Room
import im.vector.matrix.android.internal.database.mapper.RoomSummaryMapper
import im.vector.matrix.android.internal.session.room.membership.DefaultMembershipService
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor
@ -38,8 +39,8 @@ import im.vector.matrix.android.internal.session.room.state.DefaultStateService
import im.vector.matrix.android.internal.session.room.state.SendStateTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService
import im.vector.matrix.android.internal.session.room.timeline.GetContextOfEventTask
import im.vector.matrix.android.internal.session.room.timeline.InMemoryTimelineEventFactory
import im.vector.matrix.android.internal.session.room.timeline.PaginationTask
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
import im.vector.matrix.android.internal.task.TaskExecutor
import javax.inject.Inject
@ -47,6 +48,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,
private val credentials: Credentials,
private val monarchy: Monarchy,
private val eventFactory: LocalEchoEventFactory,
private val roomSummaryMapper: RoomSummaryMapper,
private val taskExecutor: TaskExecutor,
private val loadRoomMembersTask: LoadRoomMembersTask,
private val inviteTask: InviteTask,
@ -61,9 +63,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,
private val leaveRoomTask: LeaveRoomTask) {
fun create(roomId: String): Room {
val roomMemberExtractor = SenderRoomMemberExtractor(roomId)
val relationExtractor = EventRelationExtractor()
val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, relationExtractor, cryptoService)
val timelineEventFactory = InMemoryTimelineEventFactory(SenderRoomMemberExtractor(), EventRelationExtractor(), cryptoService)
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, paginationTask)
val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy)
val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask)
@ -74,6 +74,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,
return DefaultRoom(
roomId,
monarchy,
roomSummaryMapper,
timelineService,
sendService,
stateService,

View File

@ -138,4 +138,10 @@ internal abstract class RoomModule {
@Binds
abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService
@Binds
abstract fun bindSimpleTimelineEventFactory(timelineEventFactory: SimpleTimelineEventFactory): TimelineEventFactory
@Binds
abstract fun bindCacheableTimelineEventFactory(inMemoryTimelineEventFactory: InMemoryTimelineEventFactory): CacheableTimelineEventFactory
}

View File

@ -27,7 +27,6 @@ import im.vector.matrix.android.internal.database.model.RoomSummaryEntity
import im.vector.matrix.android.internal.database.query.latestEvent
import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.SessionScope
import im.vector.matrix.android.internal.session.room.membership.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary
@ -40,6 +39,23 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
private val roomDisplayNameResolver: RoomDisplayNameResolver,
private val roomAvatarResolver: RoomAvatarResolver) {
// TODO: maybe allow user of SDK to give that list
private val PREVIEWABLE_TYPES = listOf(
EventType.MESSAGE,
EventType.STATE_ROOM_NAME,
EventType.STATE_ROOM_TOPIC,
EventType.STATE_ROOM_MEMBER,
EventType.STATE_HISTORY_VISIBILITY,
EventType.CALL_INVITE,
EventType.CALL_HANGUP,
EventType.CALL_ANSWER,
EventType.ENCRYPTED,
EventType.ENCRYPTION,
EventType.STATE_ROOM_THIRD_PARTY_INVITE,
EventType.STICKER,
EventType.STATE_ROOM_CREATE
)
fun update(realm: Realm,
roomId: String,
membership: Membership? = null,
@ -47,7 +63,7 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
unreadNotifications: RoomSyncUnreadNotifications? = null) {
val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId)
?: realm.createObject(roomId)
if (roomSummary != null) {
if (roomSummary.heroes.isNotEmpty()) {
@ -71,13 +87,13 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
roomSummaryEntity.membership = membership
}
val lastEvent = EventEntity.latestEvent(realm, roomId)
val lastEvent = EventEntity.latestEvent(realm, roomId, includedTypes = PREVIEWABLE_TYPES)
val lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain()
val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId }
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic
roomSummaryEntity.lastMessage = lastEvent
roomSummaryEntity.latestEvent = lastEvent
roomSummaryEntity.otherMemberIds.clear()
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers.keys)
}

View File

@ -34,9 +34,10 @@ import io.realm.RealmList
import io.realm.RealmQuery
import javax.inject.Inject
internal class SenderRoomMemberExtractor @Inject constructor(private val roomId: String) {
internal class SenderRoomMemberExtractor @Inject constructor() {
fun extractFrom(event: EventEntity, realm: Realm = event.realm): RoomMember? {
val roomId = event.roomId
val sender = event.sender ?: return null
// If the event is unlinked we want to fetch unlinked state events
val unlinked = event.isUnlinked

View File

@ -34,6 +34,7 @@ import im.vector.matrix.android.internal.database.model.RoomEntity
import im.vector.matrix.android.internal.database.query.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.where
import im.vector.matrix.android.internal.session.content.ThumbnailExtractor
import im.vector.matrix.android.internal.session.room.RoomSummaryUpdater
import im.vector.matrix.android.internal.util.StringProvider
import im.vector.matrix.android.internal.util.tryTransactionAsync
import org.commonmark.parser.Parser
@ -50,8 +51,9 @@ import javax.inject.Inject
*
* The transactionID is used as loc
*/
internal class LocalEchoEventFactory @Inject constructor(private val credentials: Credentials, private val stringProvider: StringProvider) {
internal class LocalEchoEventFactory @Inject constructor(private val credentials: Credentials,
private val stringProvider: StringProvider,
private val roomSummaryUpdater: RoomSummaryUpdater) {
fun createTextEvent(roomId: String, msgType: String, text: String, autoMarkdown: Boolean): Event {
if (autoMarkdown && msgType == MessageType.MSGTYPE_TEXT) {
@ -342,10 +344,12 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
}
fun saveLocalEcho(monarchy: Monarchy, event: Event) {
if (event.roomId == null) throw IllegalStateException("Your event should have a roomId")
monarchy.tryTransactionAsync { realm ->
val roomEntity = RoomEntity.where(realm, roomId = event.roomId!!).findFirst()
val roomEntity = RoomEntity.where(realm, roomId = event.roomId).findFirst()
?: return@tryTransactionAsync
roomEntity.addSendingEvent(event)
roomSummaryUpdater.update(realm, event.roomId)
}
}

View File

@ -54,7 +54,7 @@ internal class DefaultTimeline(
private val realmConfiguration: RealmConfiguration,
private val taskExecutor: TaskExecutor,
private val contextOfEventTask: GetContextOfEventTask,
private val timelineEventFactory: TimelineEventFactory,
private val timelineEventFactory: CacheableTimelineEventFactory,
private val paginationTask: PaginationTask,
private val allowedTypes: List<String>?
) : Timeline {

View File

@ -33,7 +33,7 @@ import javax.inject.Inject
internal class DefaultTimelineService @Inject constructor(private val roomId: String,
private val monarchy: Monarchy,
private val taskExecutor: TaskExecutor,
private val timelineEventFactory: TimelineEventFactory,
private val timelineEventFactory: CacheableTimelineEventFactory,
private val contextOfEventTask: GetContextOfEventTask,
private val paginationTask: PaginationTask
) : TimelineService {

View File

@ -33,20 +33,46 @@ import timber.log.Timber
import java.util.*
import javax.inject.Inject
internal interface TimelineEventFactory {
fun create(eventEntity: EventEntity, realm: Realm = eventEntity.realm): TimelineEvent
}
internal interface CacheableTimelineEventFactory : TimelineEventFactory {
fun clear()
}
/**
* This class is responsible for building [TimelineEvent] returned by a [Timeline] through [TimelineService]
* It handles decryption, extracting additional data around an event as sender data and relation.
*/
internal class TimelineEventFactory @Inject constructor(
private val roomMemberExtractor: SenderRoomMemberExtractor,
private val relationExtractor: EventRelationExtractor,
private val cryptoService: CryptoService) {
internal class SimpleTimelineEventFactory @Inject constructor(private val roomMemberExtractor: SenderRoomMemberExtractor,
private val relationExtractor: EventRelationExtractor
) : TimelineEventFactory {
override fun create(eventEntity: EventEntity, realm: Realm): TimelineEvent {
val senderRoomMember = roomMemberExtractor.extractFrom(eventEntity, realm)
val relations = relationExtractor.extractFrom(eventEntity, realm)
return TimelineEvent(
eventEntity.asDomain(),
eventEntity.localId,
eventEntity.displayIndex,
senderRoomMember?.displayName,
/* TODO Rebase */ true,
senderRoomMember?.avatarUrl,
eventEntity.sendState,
relations
)
}
}
internal class InMemoryTimelineEventFactory @Inject constructor(private val roomMemberExtractor: SenderRoomMemberExtractor,
private val relationExtractor: EventRelationExtractor,
private val cryptoService: CryptoService) : CacheableTimelineEventFactory {
private val timelineId = UUID.randomUUID().toString()
private val senderCache = mutableMapOf<String, SenderData>()
private val decryptionCache = mutableMapOf<String, MXEventDecryptionResult>()
fun create(eventEntity: EventEntity, realm: Realm = eventEntity.realm): TimelineEvent {
override fun create(eventEntity: EventEntity, realm: Realm): TimelineEvent {
val sender = eventEntity.sender
val cacheKey = sender + eventEntity.localId
val senderData = senderCache.getOrPut(cacheKey) {
@ -97,7 +123,7 @@ internal class TimelineEventFactory @Inject constructor(
}
}
fun clear() {
override fun clear() {
senderCache.clear()
}