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 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.model.tag.RoomTag
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent


/** /**
* This class holds some data of a room. * This class holds some data of a room.
@ -29,7 +29,7 @@ data class RoomSummary(
val topic: String = "", val topic: String = "",
val avatarUrl: String = "", val avatarUrl: String = "",
val isDirect: Boolean = false, val isDirect: Boolean = false,
val lastMessage: Event? = null, val latestEvent: TimelineEvent? = null,
val otherMemberIds: List<String> = emptyList(), val otherMemberIds: List<String> = emptyList(),
val notificationCount: Int = 0, val notificationCount: Int = 0,
val highlightCount: 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.RoomSummary
import im.vector.matrix.android.api.session.room.model.tag.RoomTag 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.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 { fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
val tags = roomSummaryEntity.tags.map { val tags = roomSummaryEntity.tags.map {
RoomTag(it.tagName, it.tagOrder) RoomTag(it.tagName, it.tagOrder)
} }
val latestEvent = roomSummaryEntity.latestEvent?.let {
timelineEventFactory.create(it)
}
return RoomSummary( return RoomSummary(
roomId = roomSummaryEntity.roomId, roomId = roomSummaryEntity.roomId,
displayName = roomSummaryEntity.displayName ?: "", displayName = roomSummaryEntity.displayName ?: "",
topic = roomSummaryEntity.topic ?: "", topic = roomSummaryEntity.topic ?: "",
avatarUrl = roomSummaryEntity.avatarUrl ?: "", avatarUrl = roomSummaryEntity.avatarUrl ?: "",
isDirect = roomSummaryEntity.isDirect, isDirect = roomSummaryEntity.isDirect,
lastMessage = roomSummaryEntity.lastMessage?.asDomain(), latestEvent = latestEvent,
otherMemberIds = roomSummaryEntity.otherMemberIds.toList(), otherMemberIds = roomSummaryEntity.otherMemberIds.toList(),
highlightCount = roomSummaryEntity.highlightCount, highlightCount = roomSummaryEntity.highlightCount,
notificationCount = roomSummaryEntity.notificationCount, 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 displayName: String? = "",
var avatarUrl: String? = "", var avatarUrl: String? = "",
var topic: String? = "", var topic: String? = "",
var lastMessage: EventEntity? = null, var latestEvent: EventEntity? = null,
var heroes: RealmList<String> = RealmList(), var heroes: RealmList<String> = RealmList(),
var joinedMembersCount: Int? = 0, var joinedMembersCount: Int? = 0,
var invitedMembersCount: Int? = 0, var invitedMembersCount: Int? = 0,

View File

@ -16,10 +16,12 @@


package im.vector.matrix.android.internal.database.query 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.ChunkEntity
import im.vector.matrix.android.internal.database.model.EventEntity 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.EventEntity.LinkFilterMode.*
import im.vector.matrix.android.internal.database.model.EventEntityFields 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.Realm
import io.realm.RealmList import io.realm.RealmList
import io.realm.RealmQuery import io.realm.RealmQuery
@ -65,7 +67,14 @@ internal fun EventEntity.Companion.latestEvent(realm: Realm,
roomId: String, roomId: String,
includedTypes: List<String> = emptyList(), includedTypes: List<String> = emptyList(),
excludedTypes: List<String> = emptyList()): EventEntity? { 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()) { if (includedTypes.isNotEmpty()) {
query?.`in`(EventEntityFields.TYPE, includedTypes.toTypedArray()) query?.`in`(EventEntityFields.TYPE, includedTypes.toTypedArray())
} else if (excludedTypes.isNotEmpty()) { } 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.state.StateService
import im.vector.matrix.android.api.session.room.timeline.TimelineService 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.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.RoomSummaryEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields import im.vector.matrix.android.internal.database.model.RoomSummaryEntityFields
import im.vector.matrix.android.internal.database.query.where 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, internal class DefaultRoom @Inject constructor(override val roomId: String,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val roomSummaryMapper: RoomSummaryMapper,
private val timelineService: TimelineService, private val timelineService: TimelineService,
private val sendService: SendService, private val sendService: SendService,
private val stateService: StateService, 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) RoomSummaryEntity.where(realm, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME)
} }
Transformations.map(liveRealmData) { results -> Transformations.map(liveRealmData) { results ->
val roomSummaries = results.map { it.asDomain() } val roomSummaries = results.map { roomSummaryMapper.map(it) }


if (roomSummaries.isEmpty()) { if (roomSummaries.isEmpty()) {
// Create a dummy RoomSummary to avoid Crash during Sign Out or clear cache // 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? override val roomSummary: RoomSummary?
get() { get() {
var sum: RoomSummaryEntity? = monarchy.fetchCopied { RoomSummaryEntity.where(it, roomId).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME).findFirst() } 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 { 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.RoomService
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.create.CreateRoomParams 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.RoomEntity
import im.vector.matrix.android.internal.database.model.RoomSummaryEntity 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.model.RoomSummaryEntityFields
import im.vector.matrix.android.internal.database.query.where 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.session.room.create.CreateRoomTask
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import im.vector.matrix.android.internal.task.configureWith import im.vector.matrix.android.internal.task.configureWith
@ -36,6 +35,7 @@ import im.vector.matrix.android.internal.util.fetchManaged
import javax.inject.Inject import javax.inject.Inject


internal class DefaultRoomService @Inject constructor(private val monarchy: Monarchy, internal class DefaultRoomService @Inject constructor(private val monarchy: Monarchy,
private val roomSummaryMapper: RoomSummaryMapper,
private val createRoomTask: CreateRoomTask, private val createRoomTask: CreateRoomTask,
private val roomFactory: RoomFactory, private val roomFactory: RoomFactory,
private val taskExecutor: TaskExecutor) : RoomService { private val taskExecutor: TaskExecutor) : RoomService {
@ -55,7 +55,7 @@ internal class DefaultRoomService @Inject constructor(private val monarchy: Mona
override fun liveRoomSummaries(): LiveData<List<RoomSummary>> { override fun liveRoomSummaries(): LiveData<List<RoomSummary>> {
return monarchy.findAllMappedWithChanges( return monarchy.findAllMappedWithChanges(
{ realm -> RoomSummaryEntity.where(realm).isNotEmpty(RoomSummaryEntityFields.DISPLAY_NAME) }, { 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.auth.data.Credentials
import im.vector.matrix.android.api.session.crypto.CryptoService import im.vector.matrix.android.api.session.crypto.CryptoService
import im.vector.matrix.android.api.session.room.Room 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.DefaultMembershipService
import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask import im.vector.matrix.android.internal.session.room.membership.LoadRoomMembersTask
import im.vector.matrix.android.internal.session.room.membership.SenderRoomMemberExtractor 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.state.SendStateTask
import im.vector.matrix.android.internal.session.room.timeline.DefaultTimelineService 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.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.PaginationTask
import im.vector.matrix.android.internal.session.room.timeline.TimelineEventFactory
import im.vector.matrix.android.internal.task.TaskExecutor import im.vector.matrix.android.internal.task.TaskExecutor
import javax.inject.Inject import javax.inject.Inject


@ -47,6 +48,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,
private val credentials: Credentials, private val credentials: Credentials,
private val monarchy: Monarchy, private val monarchy: Monarchy,
private val eventFactory: LocalEchoEventFactory, private val eventFactory: LocalEchoEventFactory,
private val roomSummaryMapper: RoomSummaryMapper,
private val taskExecutor: TaskExecutor, private val taskExecutor: TaskExecutor,
private val loadRoomMembersTask: LoadRoomMembersTask, private val loadRoomMembersTask: LoadRoomMembersTask,
private val inviteTask: InviteTask, private val inviteTask: InviteTask,
@ -61,9 +63,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,
private val leaveRoomTask: LeaveRoomTask) { private val leaveRoomTask: LeaveRoomTask) {


fun create(roomId: String): Room { fun create(roomId: String): Room {
val roomMemberExtractor = SenderRoomMemberExtractor(roomId) val timelineEventFactory = InMemoryTimelineEventFactory(SenderRoomMemberExtractor(), EventRelationExtractor(), cryptoService)
val relationExtractor = EventRelationExtractor()
val timelineEventFactory = TimelineEventFactory(roomMemberExtractor, relationExtractor, cryptoService)
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, paginationTask) val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, timelineEventFactory, contextOfEventTask, paginationTask)
val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy) val sendService = DefaultSendService(context, credentials, roomId, eventFactory, cryptoService, monarchy)
val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask) val stateService = DefaultStateService(roomId, taskExecutor, sendStateTask)
@ -74,6 +74,7 @@ internal class RoomFactory @Inject constructor(private val context: Context,
return DefaultRoom( return DefaultRoom(
roomId, roomId,
monarchy, monarchy,
roomSummaryMapper,
timelineService, timelineService,
sendService, sendService,
stateService, stateService,

View File

@ -138,4 +138,10 @@ internal abstract class RoomModule {
@Binds @Binds
abstract fun bindTimelineService(timelineService: DefaultTimelineService): TimelineService 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.latestEvent
import im.vector.matrix.android.internal.database.query.prev import im.vector.matrix.android.internal.database.query.prev
import im.vector.matrix.android.internal.database.query.where 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.RoomDisplayNameResolver
import im.vector.matrix.android.internal.session.room.membership.RoomMembers import im.vector.matrix.android.internal.session.room.membership.RoomMembers
import im.vector.matrix.android.internal.session.sync.model.RoomSyncSummary 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 roomDisplayNameResolver: RoomDisplayNameResolver,
private val roomAvatarResolver: RoomAvatarResolver) { 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, fun update(realm: Realm,
roomId: String, roomId: String,
membership: Membership? = null, membership: Membership? = null,
@ -47,7 +63,7 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
unreadNotifications: RoomSyncUnreadNotifications? = null) { unreadNotifications: RoomSyncUnreadNotifications? = null) {


val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId) ?: realm.createObject(roomId)


if (roomSummary != null) { if (roomSummary != null) {
if (roomSummary.heroes.isNotEmpty()) { if (roomSummary.heroes.isNotEmpty()) {
@ -71,13 +87,13 @@ internal class RoomSummaryUpdater @Inject constructor(private val credentials: C
roomSummaryEntity.membership = membership 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 lastTopicEvent = EventEntity.where(realm, roomId, EventType.STATE_ROOM_TOPIC).prev()?.asDomain()
val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId } val otherRoomMembers = RoomMembers(realm, roomId).getLoaded().filterKeys { it != credentials.userId }
roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString() roomSummaryEntity.displayName = roomDisplayNameResolver.resolve(roomId).toString()
roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId) roomSummaryEntity.avatarUrl = roomAvatarResolver.resolve(roomId)
roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic roomSummaryEntity.topic = lastTopicEvent?.content.toModel<RoomTopicContent>()?.topic
roomSummaryEntity.lastMessage = lastEvent roomSummaryEntity.latestEvent = lastEvent
roomSummaryEntity.otherMemberIds.clear() roomSummaryEntity.otherMemberIds.clear()
roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers.keys) roomSummaryEntity.otherMemberIds.addAll(otherRoomMembers.keys)
} }

View File

@ -34,9 +34,10 @@ import io.realm.RealmList
import io.realm.RealmQuery import io.realm.RealmQuery
import javax.inject.Inject 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? { fun extractFrom(event: EventEntity, realm: Realm = event.realm): RoomMember? {
val roomId = event.roomId
val sender = event.sender ?: return null val sender = event.sender ?: return null
// If the event is unlinked we want to fetch unlinked state events // If the event is unlinked we want to fetch unlinked state events
val unlinked = event.isUnlinked 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.findLastLiveChunkFromRoom
import im.vector.matrix.android.internal.database.query.where 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.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.StringProvider
import im.vector.matrix.android.internal.util.tryTransactionAsync import im.vector.matrix.android.internal.util.tryTransactionAsync
import org.commonmark.parser.Parser import org.commonmark.parser.Parser
@ -50,8 +51,9 @@ import javax.inject.Inject
* *
* The transactionID is used as loc * The transactionID is used as loc
*/ */

internal class LocalEchoEventFactory @Inject constructor(private val credentials: Credentials,
internal class LocalEchoEventFactory @Inject constructor(private val credentials: Credentials, private val stringProvider: StringProvider) { private val stringProvider: StringProvider,
private val roomSummaryUpdater: RoomSummaryUpdater) {


fun createTextEvent(roomId: String, msgType: String, text: String, autoMarkdown: Boolean): Event { fun createTextEvent(roomId: String, msgType: String, text: String, autoMarkdown: Boolean): Event {
if (autoMarkdown && msgType == MessageType.MSGTYPE_TEXT) { if (autoMarkdown && msgType == MessageType.MSGTYPE_TEXT) {
@ -342,10 +344,12 @@ internal class LocalEchoEventFactory @Inject constructor(private val credentials
} }


fun saveLocalEcho(monarchy: Monarchy, event: Event) { fun saveLocalEcho(monarchy: Monarchy, event: Event) {
if (event.roomId == null) throw IllegalStateException("Your event should have a roomId")
monarchy.tryTransactionAsync { realm -> monarchy.tryTransactionAsync { realm ->
val roomEntity = RoomEntity.where(realm, roomId = event.roomId!!).findFirst() val roomEntity = RoomEntity.where(realm, roomId = event.roomId).findFirst()
?: return@tryTransactionAsync ?: return@tryTransactionAsync
roomEntity.addSendingEvent(event) roomEntity.addSendingEvent(event)
roomSummaryUpdater.update(realm, event.roomId)
} }
} }



View File

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

View File

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

View File

@ -33,20 +33,46 @@ import timber.log.Timber
import java.util.* import java.util.*
import javax.inject.Inject 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] * 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. * It handles decryption, extracting additional data around an event as sender data and relation.
*/ */
internal class TimelineEventFactory @Inject constructor( internal class SimpleTimelineEventFactory @Inject constructor(private val roomMemberExtractor: SenderRoomMemberExtractor,
private val roomMemberExtractor: SenderRoomMemberExtractor, private val relationExtractor: EventRelationExtractor
private val relationExtractor: EventRelationExtractor, ) : TimelineEventFactory {
private val cryptoService: CryptoService) { 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 timelineId = UUID.randomUUID().toString()
private val senderCache = mutableMapOf<String, SenderData>() private val senderCache = mutableMapOf<String, SenderData>()
private val decryptionCache = mutableMapOf<String, MXEventDecryptionResult>() 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 sender = eventEntity.sender
val cacheKey = sender + eventEntity.localId val cacheKey = sender + eventEntity.localId
val senderData = senderCache.getOrPut(cacheKey) { val senderData = senderCache.getOrPut(cacheKey) {
@ -97,7 +123,7 @@ internal class TimelineEventFactory @Inject constructor(
} }
} }


fun clear() { override fun clear() {
senderCache.clear() senderCache.clear()
} }



View File

@ -504,7 +504,7 @@ class RoomDetailViewModel @AssistedInject constructor(@Assisted initialState: Ro
private fun observeInvitationState() { private fun observeInvitationState() {
asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary -> asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary ->
if (summary.membership == Membership.INVITE) { if (summary.membership == Membership.INVITE) {
summary.lastMessage?.senderId?.let { senderId -> summary.latestEvent?.root?.senderId?.let { senderId ->
session.getUser(senderId) session.getUser(senderId)
}?.also { }?.also {
setState { copy(asyncInviter = Success(it)) } setState { copy(asyncInviter = Success(it)) }

View File

@ -25,14 +25,14 @@ class ChronologicalRoomComparator @Inject constructor() : Comparator<RoomSummary
var rightTimestamp = 0L var rightTimestamp = 0L
var leftTimestamp = 0L var leftTimestamp = 0L
if (null != leftRoomSummary) { if (null != leftRoomSummary) {
leftTimestamp = leftRoomSummary.lastMessage?.originServerTs ?: 0 leftTimestamp = leftRoomSummary.latestEvent?.root?.originServerTs ?: 0
} }
if (null != rightRoomSummary) { if (null != rightRoomSummary) {
rightTimestamp = rightRoomSummary.lastMessage?.originServerTs ?: 0 rightTimestamp = rightRoomSummary.latestEvent?.root?.originServerTs ?: 0
} }
return if (rightRoomSummary?.lastMessage == null) { return if (rightRoomSummary?.latestEvent?.root == null) {
-1 -1
} else if (leftRoomSummary?.lastMessage == null) { } else if (leftRoomSummary?.latestEvent?.root == null) {
1 1
} else { } else {
val deltaTimestamp = rightTimestamp - leftTimestamp val deltaTimestamp = rightTimestamp - leftTimestamp

View File

@ -22,8 +22,6 @@ sealed class RoomListActions {


data class SelectRoom(val roomSummary: RoomSummary) : RoomListActions() data class SelectRoom(val roomSummary: RoomSummary) : RoomListActions()


data class FilterRooms(val roomName: CharSequence? = null) : RoomListActions()

data class ToggleCategory(val category: RoomCategory) : RoomListActions() data class ToggleCategory(val category: RoomCategory) : RoomListActions()


} }

View File

@ -18,14 +18,11 @@ package im.vector.riotredesign.features.home.room.list


import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import arrow.core.Option
import com.airbnb.mvrx.FragmentViewModelContext import com.airbnb.mvrx.FragmentViewModelContext
import com.airbnb.mvrx.MvRxViewModelFactory import com.airbnb.mvrx.MvRxViewModelFactory
import com.airbnb.mvrx.ViewModelContext import com.airbnb.mvrx.ViewModelContext
import com.jakewharton.rxrelay2.BehaviorRelay
import com.squareup.inject.assisted.Assisted import com.squareup.inject.assisted.Assisted
import com.squareup.inject.assisted.AssistedInject import com.squareup.inject.assisted.AssistedInject
import im.vector.matrix.android.api.session.Session
import im.vector.matrix.android.api.session.room.model.Membership import im.vector.matrix.android.api.session.room.model.Membership
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.tag.RoomTag import im.vector.matrix.android.api.session.room.model.tag.RoomTag
@ -33,10 +30,7 @@ import im.vector.riotredesign.core.platform.VectorViewModel
import im.vector.riotredesign.core.utils.LiveEvent import im.vector.riotredesign.core.utils.LiveEvent
import im.vector.riotredesign.features.home.HomeRoomListObservableStore import im.vector.riotredesign.features.home.HomeRoomListObservableStore


typealias RoomListFilterName = CharSequence

class RoomListViewModel @AssistedInject constructor(@Assisted initialState: RoomListViewState, class RoomListViewModel @AssistedInject constructor(@Assisted initialState: RoomListViewState,
private val session: Session,
private val homeRoomListObservableSource: HomeRoomListObservableStore, private val homeRoomListObservableSource: HomeRoomListObservableStore,
private val alphabeticalRoomComparator: AlphabeticalRoomComparator, private val alphabeticalRoomComparator: AlphabeticalRoomComparator,
private val chronologicalRoomComparator: ChronologicalRoomComparator) private val chronologicalRoomComparator: ChronologicalRoomComparator)
@ -57,8 +51,6 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
} }


private val displayMode = initialState.displayMode private val displayMode = initialState.displayMode
private val roomListDisplayModeFilter = RoomListDisplayModeFilter(displayMode)
private val roomListFilter = BehaviorRelay.createDefault<Option<RoomListFilterName>>(Option.empty())


private val _openRoomLiveData = MutableLiveData<LiveEvent<String>>() private val _openRoomLiveData = MutableLiveData<LiveEvent<String>>()
val openRoomLiveData: LiveData<LiveEvent<String>> val openRoomLiveData: LiveData<LiveEvent<String>>
@ -71,7 +63,6 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
fun accept(action: RoomListActions) { fun accept(action: RoomListActions) {
when (action) { when (action) {
is RoomListActions.SelectRoom -> handleSelectRoom(action) is RoomListActions.SelectRoom -> handleSelectRoom(action)
is RoomListActions.FilterRooms -> handleFilterRooms(action)
is RoomListActions.ToggleCategory -> handleToggleCategory(action) is RoomListActions.ToggleCategory -> handleToggleCategory(action)
} }
} }
@ -82,11 +73,6 @@ class RoomListViewModel @AssistedInject constructor(@Assisted initialState: Room
_openRoomLiveData.postValue(LiveEvent(action.roomSummary.roomId)) _openRoomLiveData.postValue(LiveEvent(action.roomSummary.roomId))
} }


private fun handleFilterRooms(action: RoomListActions.FilterRooms) {
val optionalFilter = Option.fromNullable(action.roomName)
roomListFilter.accept(optionalFilter)
}

private fun handleToggleCategory(action: RoomListActions.ToggleCategory) = setState { private fun handleToggleCategory(action: RoomListActions.ToggleCategory) = setState {
this.toggle(action.category) this.toggle(action.category)
} }

View File

@ -18,22 +18,12 @@ package im.vector.riotredesign.features.home.room.list


import androidx.annotation.StringRes import androidx.annotation.StringRes
import com.airbnb.epoxy.TypedEpoxyController import com.airbnb.epoxy.TypedEpoxyController
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.RoomSummary import im.vector.matrix.android.api.session.room.model.RoomSummary
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.core.resources.DateProvider
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.AvatarRenderer
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
import javax.inject.Inject import javax.inject.Inject


class RoomSummaryController @Inject constructor(private val stringProvider: StringProvider, class RoomSummaryController @Inject constructor(private val stringProvider: StringProvider,
private val eventFormatter: NoticeEventFormatter, private val roomSummaryItemFactory: RoomSummaryItemFactory
private val timelineDateFormatter: TimelineDateFormatter,
private val avatarRenderer: AvatarRenderer
) : TypedEpoxyController<RoomListViewState>() { ) : TypedEpoxyController<RoomListViewState>() {


var callback: Callback? = null var callback: Callback? = null
@ -85,44 +75,9 @@ class RoomSummaryController @Inject constructor(private val stringProvider: Stri


private fun buildRoomModels(summaries: List<RoomSummary>) { private fun buildRoomModels(summaries: List<RoomSummary>) {
summaries.forEach { roomSummary -> summaries.forEach { roomSummary ->
val unreadCount = roomSummary.notificationCount roomSummaryItemFactory
val showHighlighted = roomSummary.highlightCount > 0 .create(roomSummary) { callback?.onRoomSelected(it) }

.addTo(this)
var lastMessageFormatted: CharSequence = ""
var lastMessageTime: CharSequence = ""
val lastMessage = roomSummary.lastMessage
if (lastMessage != null) {
val date = lastMessage.localDateTime()
val currentData = DateProvider.currentLocalDateTime()
val isSameDay = date.toLocalDate() == currentData.toLocalDate()
//TODO: get formatted
if (lastMessage.type == EventType.MESSAGE) {
val content = lastMessage.content?.toModel<MessageContent>()
lastMessageFormatted = content?.body ?: ""
} else {
lastMessageFormatted = lastMessage.type
}
lastMessageTime = if (isSameDay) {
timelineDateFormatter.formatMessageHour(date)
} else {
//TODO: change this
timelineDateFormatter.formatMessageDay(date)
}


}
roomSummaryItem {
avatarRenderer(avatarRenderer)
id(roomSummary.roomId)
roomId(roomSummary.roomId)
lastEventTime(lastMessageTime)
lastFormattedEvent(lastMessageFormatted)
roomName(roomSummary.displayName)
avatarUrl(roomSummary.avatarUrl)
showHighlighted(showHighlighted)
unreadCount(unreadCount)
listener { callback?.onRoomSelected(roomSummary) }
}
} }
} }



View File

@ -0,0 +1,86 @@
/*
* 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.riotredesign.features.home.room.list

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.RoomSummary
import im.vector.matrix.android.api.session.room.model.message.MessageContent
import im.vector.riotredesign.R
import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.core.resources.ColorProvider
import im.vector.riotredesign.core.resources.DateProvider
import im.vector.riotredesign.features.home.room.detail.timeline.format.NoticeEventFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.senderName
import me.gujun.android.span.span
import javax.inject.Inject

class RoomSummaryItemFactory @Inject constructor(private val noticeEventFormatter: NoticeEventFormatter,
private val timelineDateFormatter: TimelineDateFormatter,
private val colorProvider: ColorProvider) {

fun create(roomSummary: RoomSummary, onRoomSelected: (RoomSummary) -> Unit): RoomSummaryItem {
val unreadCount = roomSummary.notificationCount
val showHighlighted = roomSummary.highlightCount > 0

var latestFormattedEvent: CharSequence = ""
var latestEventTime: CharSequence = ""
val latestEvent = roomSummary.latestEvent
if (latestEvent != null) {
val date = latestEvent.root.localDateTime()
val currentData = DateProvider.currentLocalDateTime()
val isSameDay = date.toLocalDate() == currentData.toLocalDate()
latestFormattedEvent = if (latestEvent.root.type == EventType.MESSAGE) {
val senderName = latestEvent.senderName() ?: latestEvent.root.senderId
val content = latestEvent.root.content?.toModel<MessageContent>()
val message = content?.body ?: ""
if (roomSummary.isDirect.not() && senderName != null) {
span {
text = senderName
textColor = colorProvider.getColorFromAttribute(R.attr.riotx_text_primary)
}
.append(" - ")
.append(message)
} else {
message
}
} else {
span {
text = noticeEventFormatter.format(latestEvent) ?: ""
textStyle = "italic"
}
}
latestEventTime = if (isSameDay) {
timelineDateFormatter.formatMessageHour(date)
} else {
//TODO: change this
timelineDateFormatter.formatMessageDay(date)
}
}
return RoomSummaryItem_()
.id(roomSummary.roomId)
.roomId(roomSummary.roomId)
.lastEventTime(latestEventTime)
.lastFormattedEvent(latestFormattedEvent)
.roomName(roomSummary.displayName)
.avatarUrl(roomSummary.avatarUrl)
.showHighlighted(showHighlighted)
.unreadCount(unreadCount)
.listener { onRoomSelected(roomSummary) }
}
}