Timeline : fix some timeline rendering issues (senderName, merge item, left event). Still need to work on it.

This commit is contained in:
ganfra 2019-04-30 19:55:55 +02:00
parent 287feace12
commit 694df9d845
17 changed files with 210 additions and 128 deletions

View file

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

View file

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

View file

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

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.invite.InviteTask
import im.vector.matrix.android.internal.session.room.members.DefaultRoomMembersService 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.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.DefaultReadService
import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask import im.vector.matrix.android.internal.session.room.read.SetReadMarkersTask
import im.vector.matrix.android.internal.session.room.send.DefaultSendService 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) { private val taskExecutor: TaskExecutor) {
fun instantiate(roomId: String): Room { fun instantiate(roomId: String): Room {
val roomMemberExtractor = RoomMemberExtractor(roomId) val roomMemberExtractor = SenderRoomMemberExtractor(roomId)
val timelineEventFactory = TimelineEventFactory(roomMemberExtractor) val timelineEventFactory = TimelineEventFactory(roomMemberExtractor)
val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, contextOfEventTask, timelineEventFactory, paginationTask) val timelineService = DefaultTimelineService(roomId, monarchy, taskExecutor, contextOfEventTask, timelineEventFactory, paginationTask)
val sendService = DefaultSendService(roomId, eventFactory, monarchy) 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> { fun queryRoomMembersEvent(): RealmQuery<EventEntity> {
return EventEntity return EventEntity
.where(realm, roomId, EventType.STATE_ROOM_MEMBER) .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.events.model.toModel
import im.vector.matrix.android.api.session.room.model.RoomMember 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.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.EventEntity
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 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.next
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 io.realm.Realm import io.realm.RealmList
import io.realm.RealmQuery import io.realm.RealmQuery
internal class RoomMemberExtractor(private val roomId: String) { internal class SenderRoomMemberExtractor(private val roomId: String) {
private val cached = HashMap<String, RoomMember?>()
fun extractFrom(event: EventEntity): RoomMember? { fun extractFrom(event: EventEntity): RoomMember? {
val sender = event.sender ?: return null 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 // If the event is unlinked we want to fetch unlinked state events
val unlinked = event.isUnlinked val unlinked = event.isUnlinked
// When stateIndex is negative, we try to get the next stateEvent prevContent() val roomEntity = RoomEntity.where(event.realm, roomId = roomId).findFirst() ?: return null
// If prevContent is null we fallback to the Int.MIN state events content() val chunkEntity = ChunkEntity.findIncludingEvent(event.realm, event.eventId)
val content = if (event.stateIndex <= 0) { val content = when {
baseQuery(event.realm, roomId, sender, unlinked).next(from = event.stateIndex)?.prevContent chunkEntity == null -> null
?: baseQuery(event.realm, roomId, sender, unlinked).prev(since = event.stateIndex)?.content event.stateIndex <= 0 -> baseQuery(chunkEntity.events, sender, unlinked).next(from = event.stateIndex)?.prevContent
} else { else -> baseQuery(chunkEntity.events, sender, unlinked).prev(since = event.stateIndex)?.content
baseQuery(event.realm, roomId, sender, unlinked).prev(since = event.stateIndex)?.content
}
val roomMember: RoomMember? = ContentMapper.map(content).toModel()
cached[cacheKey] = roomMember
return roomMember
} }
private fun baseQuery(realm: Realm, val fallbackContent = content
roomId: String, ?: baseQuery(roomEntity.untimelinedStateEvents, sender, unlinked).prev(since = event.stateIndex)?.content
return ContentMapper.map(fallbackContent).toModel()
}
private fun baseQuery(list: RealmList<EventEntity>,
sender: String, sender: String,
isUnlinked: Boolean): RealmQuery<EventEntity> { isUnlinked: Boolean): RealmQuery<EventEntity> {
return list
val filterMode = if (isUnlinked) EventEntity.LinkFilterMode.UNLINKED_ONLY else EventEntity.LinkFilterMode.LINKED_ONLY .where()
return EventEntity
.where(realm, roomId = roomId, type = EventType.STATE_ROOM_MEMBER, linkFilterMode = filterMode)
.equalTo(EventEntityFields.STATE_KEY, sender) .equalTo(EventEntityFields.STATE_KEY, sender)
.equalTo(EventEntityFields.TYPE, EventType.STATE_ROOM_MEMBER)
.equalTo(EventEntityFields.IS_UNLINKED, isUnlinked)
} }
} }

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.session.room.timeline.TimelineEvent
import im.vector.matrix.android.api.util.CancelableBag import im.vector.matrix.android.api.util.CancelableBag
import im.vector.matrix.android.api.util.addTo 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.findIncludingEvent
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.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
import im.vector.matrix.android.internal.util.Debouncer 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 timber.log.Timber
import java.util.* import java.util.*
import java.util.concurrent.atomic.AtomicBoolean import java.util.concurrent.atomic.AtomicBoolean
@ -90,6 +100,13 @@ internal class DefaultTimeline(
if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL ) { if (changeSet.state == OrderedCollectionChangeSet.State.INITIAL ) {
handleInitialLoad() handleInitialLoad()
} else { } 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 -> changeSet.insertionRanges.forEach { range ->
val (startDisplayIndex, direction) = if (range.startIndex == 0) { val (startDisplayIndex, direction) = if (range.startIndex == 0) {
Pair(liveEvents[range.length - 1]!!.displayIndex, Timeline.Direction.FORWARDS) Pair(liveEvents[range.length - 1]!!.displayIndex, Timeline.Direction.FORWARDS)
@ -108,6 +125,7 @@ internal class DefaultTimeline(
buildTimelineEvents(startDisplayIndex, direction, range.length.toLong()) buildTimelineEvents(startDisplayIndex, direction, range.length.toLong())
postSnapshot() postSnapshot()
} }
} }
} }
} }

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.api.session.room.timeline.TimelineEvent
import im.vector.matrix.android.internal.database.mapper.asDomain import im.vector.matrix.android.internal.database.mapper.asDomain
import im.vector.matrix.android.internal.database.model.EventEntity 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 { 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( return TimelineEvent(
eventEntity.asDomain(), eventEntity.asDomain(),
eventEntity.localId, eventEntity.localId,
eventEntity.displayIndex, eventEntity.displayIndex,
roomMember, senderData.senderName,
senderData.senderAvatar,
eventEntity.sendState eventEntity.sendState
) )
} }
fun clear(){
cached.clear()
}
private data class SenderData(
val senderName: String?,
val senderAvatar: String?
)
} }

View file

@ -24,6 +24,8 @@ import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyController import com.airbnb.epoxy.EpoxyController
import com.airbnb.epoxy.EpoxyModel import com.airbnb.epoxy.EpoxyModel
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.api.session.room.model.message.MessageAudioContent import im.vector.matrix.android.api.session.room.model.message.MessageAudioContent
import im.vector.matrix.android.api.session.room.model.message.MessageFileContent 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.MessageImageContent
@ -33,7 +35,15 @@ import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.core.epoxy.LoadingItemModel_ import im.vector.riotredesign.core.epoxy.LoadingItemModel_
import im.vector.riotredesign.core.extensions.localDateTime import im.vector.riotredesign.core.extensions.localDateTime
import im.vector.riotredesign.features.home.room.detail.timeline.factory.TimelineItemFactory import im.vector.riotredesign.features.home.room.detail.timeline.factory.TimelineItemFactory
import im.vector.riotredesign.features.home.room.detail.timeline.helper.* import im.vector.riotredesign.features.home.room.detail.timeline.helper.RoomMemberEventHelper
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineAsyncHelper
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineDateFormatter
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineEventDiffUtilCallback
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineEventVisibilityStateChangedListener
import im.vector.riotredesign.features.home.room.detail.timeline.helper.TimelineMediaSizeProvider
import im.vector.riotredesign.features.home.room.detail.timeline.helper.canBeMerged
import im.vector.riotredesign.features.home.room.detail.timeline.helper.nextDisplayableEvent
import im.vector.riotredesign.features.home.room.detail.timeline.helper.prevSameTypeEvents
import im.vector.riotredesign.features.home.room.detail.timeline.item.DaySeparatorItem import im.vector.riotredesign.features.home.room.detail.timeline.item.DaySeparatorItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.DaySeparatorItem_ import im.vector.riotredesign.features.home.room.detail.timeline.item.DaySeparatorItem_
import im.vector.riotredesign.features.home.room.detail.timeline.item.MergedHeaderItem import im.vector.riotredesign.features.home.room.detail.timeline.item.MergedHeaderItem
@ -217,24 +227,32 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
if (prevSameTypeEvents.isEmpty()) { if (prevSameTypeEvents.isEmpty()) {
null null
} else { } else {
val mergedEvents = (listOf(event) + prevSameTypeEvents) val mergedEvents = (prevSameTypeEvents + listOf(event)).asReversed()
val mergedData = mergedEvents.map { val mergedData = mergedEvents.map { mergedEvent ->
val roomMember = event.roomMember val eventContent: RoomMember? = mergedEvent.root.content.toModel()
val prevEventContent: RoomMember? = mergedEvent.root.prevContent.toModel()
val senderAvatar = RoomMemberEventHelper.senderAvatar(eventContent, prevEventContent, mergedEvent)
val senderName = RoomMemberEventHelper.senderName(eventContent, prevEventContent, mergedEvent)
MergedHeaderItem.Data( MergedHeaderItem.Data(
userId = event.root.sender ?: "", userId = mergedEvent.root.sender ?: "",
avatarUrl = roomMember?.avatarUrl, avatarUrl = senderAvatar,
memberName = roomMember?.displayName ?: "", memberName = senderName ?: "",
eventId = it.localId eventId = mergedEvent.localId
) )
} }
val mergedEventIds = mergedEvents.map { it.localId } val mergedEventIds = mergedEvents.map { it.localId }
val mergeId = mergedEventIds.joinToString(separator = "_") { it } // We try to find if one of the item id were used as mergeItemCollapseStates key
val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { true } // => handle case where paginating from mergeable events and we get more
val previousCollapseStateKey = mergedEventIds.intersect(mergeItemCollapseStates.keys).firstOrNull()
val initialCollapseState = mergeItemCollapseStates.remove(previousCollapseStateKey)
?: true
val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { initialCollapseState }
if (isCollapsed) { if (isCollapsed) {
collapsedEventIds.addAll(mergedEventIds) collapsedEventIds.addAll(mergedEventIds)
} else { } else {
collapsedEventIds.removeAll(mergedEventIds) collapsedEventIds.removeAll(mergedEventIds)
} }
val mergeId = mergedEventIds.joinToString(separator = "_") { it }
MergedHeaderItem(isCollapsed, mergeId, mergedData) { MergedHeaderItem(isCollapsed, mergeId, mergedData) {
mergeItemCollapseStates[event.localId] = it mergeItemCollapseStates[event.localId] = it
requestModelBuild() requestModelBuild()

View file

@ -30,27 +30,26 @@ import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem
class CallItemFactory(private val stringProvider: StringProvider) { class CallItemFactory(private val stringProvider: StringProvider) {
fun create(event: TimelineEvent): NoticeItem? { fun create(event: TimelineEvent): NoticeItem? {
val roomMember = event.roomMember ?: return null val text = buildNoticeText(event.root, event.senderName) ?: return null
val text = buildNoticeText(event.root, roomMember) ?: return null
return NoticeItem_() return NoticeItem_()
.noticeText(text) .noticeText(text)
.avatarUrl(roomMember.avatarUrl) .avatarUrl(event.senderAvatar)
.memberName(roomMember.displayName) .memberName(event.senderName)
} }
private fun buildNoticeText(event: Event, roomMember: RoomMember): CharSequence? { private fun buildNoticeText(event: Event, senderName: String?): CharSequence? {
return when { return when {
EventType.CALL_INVITE == event.type -> { EventType.CALL_INVITE == event.type -> {
val content = event.content.toModel<CallInviteContent>() ?: return null val content = event.content.toModel<CallInviteContent>() ?: return null
val isVideoCall = content.offer.sdp == CallInviteContent.Offer.SDP_VIDEO val isVideoCall = content.offer.sdp == CallInviteContent.Offer.SDP_VIDEO
return if (isVideoCall) { return if (isVideoCall) {
stringProvider.getString(R.string.notice_placed_video_call, roomMember.displayName) stringProvider.getString(R.string.notice_placed_video_call, senderName)
} else { } else {
stringProvider.getString(R.string.notice_placed_voice_call, roomMember.displayName) stringProvider.getString(R.string.notice_placed_voice_call, senderName)
} }
} }
EventType.CALL_ANSWER == event.type -> stringProvider.getString(R.string.notice_answered_call, roomMember.displayName) EventType.CALL_ANSWER == event.type -> stringProvider.getString(R.string.notice_answered_call, senderName)
EventType.CALL_HANGUP == event.type -> stringProvider.getString(R.string.notice_ended_call, roomMember.displayName) EventType.CALL_HANGUP == event.type -> stringProvider.getString(R.string.notice_ended_call, senderName)
else -> null else -> null
} }

View file

@ -65,8 +65,6 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
): VectorEpoxyModel<*>? { ): VectorEpoxyModel<*>? {
val eventId = event.root.eventId ?: return null val eventId = event.root.eventId ?: return null
val roomMember = event.roomMember
val nextRoomMember = nextEvent?.roomMember
val date = event.root.localDateTime() val date = event.root.localDateTime()
val nextDate = nextEvent?.root?.localDateTime() val nextDate = nextEvent?.root?.localDateTime()
@ -75,14 +73,15 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
?: false ?: false
val showInformation = addDaySeparator val showInformation = addDaySeparator
|| nextRoomMember != roomMember || event.senderAvatar != nextEvent?.senderAvatar
|| event.senderName != nextEvent?.senderName
|| nextEvent?.root?.type != EventType.MESSAGE || nextEvent?.root?.type != EventType.MESSAGE
|| isNextMessageReceivedMoreThanOneHourAgo || isNextMessageReceivedMoreThanOneHourAgo
val messageContent: MessageContent = event.root.content.toModel() ?: return null val messageContent: MessageContent = event.root.content.toModel() ?: return null
val time = timelineDateFormatter.formatMessageHour(date) val time = timelineDateFormatter.formatMessageHour(date)
val avatarUrl = roomMember?.avatarUrl val avatarUrl = event.senderAvatar
val memberName = roomMember?.displayName ?: event.root.sender ?: "" val memberName = event.senderName ?: event.root.sender ?: ""
val formattedMemberName = span(memberName) { val formattedMemberName = span(memberName) {
textColor = colorProvider.getColor(getColorFor(event.root.sender ?: "")) textColor = colorProvider.getColor(getColorFor(event.root.sender ?: ""))
} }

View file

@ -31,15 +31,14 @@ import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem
class RoomHistoryVisibilityItemFactory(private val stringProvider: StringProvider) { class RoomHistoryVisibilityItemFactory(private val stringProvider: StringProvider) {
fun create(event: TimelineEvent): NoticeItem? { fun create(event: TimelineEvent): NoticeItem? {
val roomMember = event.roomMember ?: return null val noticeText = buildNoticeText(event.root, event.senderName) ?: return null
val noticeText = buildNoticeText(event.root, roomMember) ?: return null
return NoticeItem_() return NoticeItem_()
.noticeText(noticeText) .noticeText(noticeText)
.avatarUrl(roomMember.avatarUrl) .avatarUrl(event.senderAvatar)
.memberName(roomMember.displayName) .memberName(event.senderName)
} }
private fun buildNoticeText(event: Event, roomMember: RoomMember): CharSequence? { private fun buildNoticeText(event: Event, senderName: String?): CharSequence? {
val content = event.content.toModel<RoomHistoryVisibilityContent>() ?: return null val content = event.content.toModel<RoomHistoryVisibilityContent>() ?: return null
val formattedVisibility = when (content.historyVisibility) { val formattedVisibility = when (content.historyVisibility) {
RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared) RoomHistoryVisibility.SHARED -> stringProvider.getString(R.string.notice_room_visibility_shared)
@ -47,7 +46,7 @@ class RoomHistoryVisibilityItemFactory(private val stringProvider: StringProvide
RoomHistoryVisibility.JOINED -> stringProvider.getString(R.string.notice_room_visibility_joined) RoomHistoryVisibility.JOINED -> stringProvider.getString(R.string.notice_room_visibility_joined)
RoomHistoryVisibility.WORLD_READABLE -> stringProvider.getString(R.string.notice_room_visibility_world_readable) RoomHistoryVisibility.WORLD_READABLE -> stringProvider.getString(R.string.notice_room_visibility_world_readable)
} }
return stringProvider.getString(R.string.notice_made_future_room_visibility, roomMember.displayName, formattedVisibility) return stringProvider.getString(R.string.notice_made_future_room_visibility, senderName, formattedVisibility)
} }

View file

@ -23,6 +23,7 @@ import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
import im.vector.riotredesign.R import im.vector.riotredesign.R
import im.vector.riotredesign.core.resources.StringProvider import im.vector.riotredesign.core.resources.StringProvider
import im.vector.riotredesign.features.home.room.detail.timeline.helper.RoomMemberEventHelper
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem
import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_ import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem_
@ -31,18 +32,20 @@ import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem
class RoomMemberItemFactory(private val stringProvider: StringProvider) { class RoomMemberItemFactory(private val stringProvider: StringProvider) {
fun create(event: TimelineEvent): NoticeItem? { fun create(event: TimelineEvent): NoticeItem? {
val roomMember = event.roomMember ?: return null val eventContent: RoomMember? = event.root.content.toModel()
val noticeText = buildRoomMemberNotice(event) ?: return null val prevEventContent: RoomMember? = event.root.prevContent.toModel()
val noticeText = buildRoomMemberNotice(event, eventContent, prevEventContent) ?: return null
val senderAvatar = RoomMemberEventHelper.senderAvatar(eventContent, prevEventContent, event)
val senderName = RoomMemberEventHelper.senderName(eventContent, prevEventContent, event)
return NoticeItem_() return NoticeItem_()
.userId(event.root.sender ?: "") .userId(event.root.sender ?: "")
.noticeText(noticeText) .noticeText(noticeText)
.avatarUrl(roomMember.avatarUrl) .avatarUrl(senderAvatar)
.memberName(roomMember.displayName) .memberName(senderName)
} }
private fun buildRoomMemberNotice(event: TimelineEvent): String? { private fun buildRoomMemberNotice(event: TimelineEvent, eventContent: RoomMember?, prevEventContent: RoomMember?): String? {
val eventContent: RoomMember? = event.root.content.toModel()
val prevEventContent: RoomMember? = event.root.prevContent.toModel()
val isMembershipEvent = prevEventContent?.membership != eventContent?.membership val isMembershipEvent = prevEventContent?.membership != eventContent?.membership
return if (isMembershipEvent) { return if (isMembershipEvent) {
buildMembershipNotice(event, eventContent, prevEventContent) buildMembershipNotice(event, eventContent, prevEventContent)
@ -72,7 +75,7 @@ class RoomMemberItemFactory(private val stringProvider: StringProvider) {
displayText.append(" ") displayText.append(" ")
stringProvider.getString(R.string.notice_avatar_changed_too) stringProvider.getString(R.string.notice_avatar_changed_too)
} else { } else {
stringProvider.getString(R.string.notice_avatar_url_changed, event.roomMember?.displayName) stringProvider.getString(R.string.notice_avatar_url_changed, event.senderName)
} }
displayText.append(displayAvatarText) displayText.append(displayAvatarText)
} }
@ -80,12 +83,12 @@ class RoomMemberItemFactory(private val stringProvider: StringProvider) {
} }
private fun buildMembershipNotice(event: TimelineEvent, eventContent: RoomMember?, prevEventContent: RoomMember?): String? { private fun buildMembershipNotice(event: TimelineEvent, eventContent: RoomMember?, prevEventContent: RoomMember?): String? {
val senderDisplayName = event.roomMember?.displayName ?: return null val senderDisplayName = event.senderName ?: event.root.sender
val targetDisplayName = eventContent?.displayName ?: event.root.sender val targetDisplayName = eventContent?.displayName ?: event.root.sender
return when { return when {
Membership.INVITE == eventContent?.membership -> { Membership.INVITE == eventContent?.membership -> {
// TODO get userId // TODO get userId
val selfUserId: String = "" val selfUserId = ""
when { when {
eventContent.thirdPartyInvite != null -> eventContent.thirdPartyInvite != null ->
stringProvider.getString(R.string.notice_room_third_party_registered_invite, stringProvider.getString(R.string.notice_room_third_party_registered_invite,
@ -106,7 +109,8 @@ class RoomMemberItemFactory(private val stringProvider: StringProvider) {
if (prevEventContent?.membership == Membership.INVITE) { if (prevEventContent?.membership == Membership.INVITE) {
stringProvider.getString(R.string.notice_room_reject, senderDisplayName) stringProvider.getString(R.string.notice_room_reject, senderDisplayName)
} else { } else {
stringProvider.getString(R.string.notice_room_leave, senderDisplayName) val leftDisplayName = RoomMemberEventHelper.senderName(eventContent, prevEventContent, event)
stringProvider.getString(R.string.notice_room_leave, leftDisplayName)
} }
} else if (prevEventContent?.membership == Membership.INVITE) { } else if (prevEventContent?.membership == Membership.INVITE) {
stringProvider.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName) stringProvider.getString(R.string.notice_room_withdraw, senderDisplayName, targetDisplayName)

View file

@ -29,20 +29,16 @@ class RoomNameItemFactory(private val stringProvider: StringProvider) {
fun create(event: TimelineEvent): NoticeItem? { fun create(event: TimelineEvent): NoticeItem? {
val content: RoomNameContent? = event.root.content.toModel() val content: RoomNameContent = event.root.content.toModel() ?: return null
val roomMember = event.roomMember
if (content == null || roomMember == null) {
return null
}
val text = if (!TextUtils.isEmpty(content.name)) { val text = if (!TextUtils.isEmpty(content.name)) {
stringProvider.getString(R.string.notice_room_name_changed, roomMember.displayName, content.name) stringProvider.getString(R.string.notice_room_name_changed, event.senderName, content.name)
} else { } else {
stringProvider.getString(R.string.notice_room_name_removed, roomMember.displayName) stringProvider.getString(R.string.notice_room_name_removed, event.senderName)
} }
return NoticeItem_() return NoticeItem_()
.noticeText(text) .noticeText(text)
.avatarUrl(roomMember.avatarUrl) .avatarUrl(event.senderAvatar)
.memberName(roomMember.displayName) .memberName(event.senderName)
} }

View file

@ -28,20 +28,16 @@ class RoomTopicItemFactory(private val stringProvider: StringProvider) {
fun create(event: TimelineEvent): NoticeItem? { fun create(event: TimelineEvent): NoticeItem? {
val content: RoomTopicContent? = event.root.content.toModel() val content: RoomTopicContent = event.root.content.toModel() ?: return null
val roomMember = event.roomMember
if (content == null || roomMember == null) {
return null
}
val text = if (content.topic.isNullOrEmpty()) { val text = if (content.topic.isNullOrEmpty()) {
stringProvider.getString(R.string.notice_room_topic_removed, roomMember.displayName) stringProvider.getString(R.string.notice_room_topic_removed, event.senderName)
} else { } else {
stringProvider.getString(R.string.notice_room_topic_changed, roomMember.displayName, content.topic) stringProvider.getString(R.string.notice_room_topic_changed, event.senderName, content.topic)
} }
return NoticeItem_() return NoticeItem_()
.noticeText(text) .noticeText(text)
.avatarUrl(roomMember.avatarUrl) .avatarUrl(event.senderAvatar)
.memberName(roomMember.displayName) .memberName(event.senderName)
} }

View file

@ -34,28 +34,18 @@ class EndlessRecyclerViewScrollListener(private val layoutManager: LinearLayoutM
// This happens many times a second during a scroll, so be wary of the code you place here. // This happens many times a second during a scroll, so be wary of the code you place here.
// We are given a few useful parameters to help us work out if we need to load some more data, // We are given a few useful parameters to help us work out if we need to load some more data,
// but first we check if we are waiting for the previous load to finish. // but first we check if we are waiting for the previous load to finish.
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition() val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition() val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val totalItemCount = layoutManager.itemCount val totalItemCount = layoutManager.itemCount
// The minimum amount of items to have below your current scroll position // We check to see if the dataset count has
// before loading more.
// If the total item count is zero and the previous isn't, assume the
// list is invalidated and should be reset back to initial state
if (totalItemCount < previousTotalItemCount) {
previousTotalItemCount = totalItemCount
if (totalItemCount == 0) {
loadingForwards = true
loadingBackwards = true
}
}
// If its still loading, we check to see if the dataset count has
// changed, if so we conclude it has finished loading // changed, if so we conclude it has finished loading
if (totalItemCount > previousTotalItemCount) { if (totalItemCount != previousTotalItemCount) {
previousTotalItemCount = totalItemCount
loadingBackwards = false loadingBackwards = false
loadingForwards = false loadingForwards = false
previousTotalItemCount = totalItemCount
} }
// If it isnt currently loading, we check to see if we have reached // If it isnt currently loading, we check to see if we have reached
// the visibleThreshold and need to reload more data. // the visibleThreshold and need to reload more data.

View file

@ -0,0 +1,40 @@
/*
* 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.detail.timeline.helper
import im.vector.matrix.android.api.session.room.model.Membership
import im.vector.matrix.android.api.session.room.model.RoomMember
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
object RoomMemberEventHelper {
fun senderAvatar(eventContent: RoomMember?, prevEventContent: RoomMember?, event: TimelineEvent): String? {
return if (eventContent?.membership == Membership.LEAVE && eventContent.avatarUrl == null && prevEventContent?.avatarUrl != null) {
prevEventContent.avatarUrl
} else {
event.senderAvatar
}
}
fun senderName(eventContent: RoomMember?, prevEventContent: RoomMember?, event: TimelineEvent): String? {
return if (eventContent?.membership == Membership.LEAVE && eventContent.displayName == null && prevEventContent?.displayName != null) {
prevEventContent.displayName
} else {
event.senderName
}
}
}