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

@ -24,6 +24,8 @@ import androidx.recyclerview.widget.ListUpdateCallback
import androidx.recyclerview.widget.RecyclerView
import com.airbnb.epoxy.EpoxyController
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.MessageFileContent
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.extensions.localDateTime
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.MergedHeaderItem
@ -160,8 +170,8 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
// Should be build if not cached or if cached but contains mergedHeader or formattedDay
// We then are sure we always have items up to date.
if (modelCache[position] == null
|| modelCache[position]?.mergedHeaderModel != null
|| modelCache[position]?.formattedDayModel != null) {
|| modelCache[position]?.mergedHeaderModel != null
|| modelCache[position]?.formattedDayModel != null) {
modelCache[position] = buildItemModels(position, currentSnapshot)
}
}
@ -217,24 +227,32 @@ class TimelineEventController(private val dateFormatter: TimelineDateFormatter,
if (prevSameTypeEvents.isEmpty()) {
null
} else {
val mergedEvents = (listOf(event) + prevSameTypeEvents)
val mergedData = mergedEvents.map {
val roomMember = event.roomMember
val mergedEvents = (prevSameTypeEvents + listOf(event)).asReversed()
val mergedData = mergedEvents.map { mergedEvent ->
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(
userId = event.root.sender ?: "",
avatarUrl = roomMember?.avatarUrl,
memberName = roomMember?.displayName ?: "",
eventId = it.localId
userId = mergedEvent.root.sender ?: "",
avatarUrl = senderAvatar,
memberName = senderName ?: "",
eventId = mergedEvent.localId
)
}
val mergedEventIds = mergedEvents.map { it.localId }
val mergeId = mergedEventIds.joinToString(separator = "_") { it }
val isCollapsed = mergeItemCollapseStates.getOrPut(event.localId) { true }
// We try to find if one of the item id were used as mergeItemCollapseStates key
// => 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) {
collapsedEventIds.addAll(mergedEventIds)
} else {
collapsedEventIds.removeAll(mergedEventIds)
}
val mergeId = mergedEventIds.joinToString(separator = "_") { it }
MergedHeaderItem(isCollapsed, mergeId, mergedData) {
mergeItemCollapseStates[event.localId] = it
requestModelBuild()

View File

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

View File

@ -65,8 +65,6 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
): VectorEpoxyModel<*>? {
val eventId = event.root.eventId ?: return null
val roomMember = event.roomMember
val nextRoomMember = nextEvent?.roomMember
val date = event.root.localDateTime()
val nextDate = nextEvent?.root?.localDateTime()
@ -75,14 +73,15 @@ class MessageItemFactory(private val colorProvider: ColorProvider,
?: false
val showInformation = addDaySeparator
|| nextRoomMember != roomMember
|| event.senderAvatar != nextEvent?.senderAvatar
|| event.senderName != nextEvent?.senderName
|| nextEvent?.root?.type != EventType.MESSAGE
|| isNextMessageReceivedMoreThanOneHourAgo
val messageContent: MessageContent = event.root.content.toModel() ?: return null
val time = timelineDateFormatter.formatMessageHour(date)
val avatarUrl = roomMember?.avatarUrl
val memberName = roomMember?.displayName ?: event.root.sender ?: ""
val avatarUrl = event.senderAvatar
val memberName = event.senderName ?: event.root.sender ?: ""
val formattedMemberName = span(memberName) {
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) {
fun create(event: TimelineEvent): NoticeItem? {
val roomMember = event.roomMember ?: return null
val noticeText = buildNoticeText(event.root, roomMember) ?: return null
val noticeText = buildNoticeText(event.root, event.senderName) ?: return null
return NoticeItem_()
.noticeText(noticeText)
.avatarUrl(roomMember.avatarUrl)
.memberName(roomMember.displayName)
.avatarUrl(event.senderAvatar)
.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 formattedVisibility = when (content.historyVisibility) {
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.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.riotredesign.R
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_
@ -31,18 +32,20 @@ import im.vector.riotredesign.features.home.room.detail.timeline.item.NoticeItem
class RoomMemberItemFactory(private val stringProvider: StringProvider) {
fun create(event: TimelineEvent): NoticeItem? {
val roomMember = event.roomMember ?: return null
val noticeText = buildRoomMemberNotice(event) ?: return null
val eventContent: RoomMember? = event.root.content.toModel()
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_()
.userId(event.root.sender ?: "")
.noticeText(noticeText)
.avatarUrl(roomMember.avatarUrl)
.memberName(roomMember.displayName)
.avatarUrl(senderAvatar)
.memberName(senderName)
}
private fun buildRoomMemberNotice(event: TimelineEvent): String? {
val eventContent: RoomMember? = event.root.content.toModel()
val prevEventContent: RoomMember? = event.root.prevContent.toModel()
private fun buildRoomMemberNotice(event: TimelineEvent, eventContent: RoomMember?, prevEventContent: RoomMember?): String? {
val isMembershipEvent = prevEventContent?.membership != eventContent?.membership
return if (isMembershipEvent) {
buildMembershipNotice(event, eventContent, prevEventContent)
@ -62,7 +65,7 @@ class RoomMemberItemFactory(private val stringProvider: StringProvider) {
stringProvider.getString(R.string.notice_display_name_removed, event.root.sender, prevEventContent?.displayName)
else ->
stringProvider.getString(R.string.notice_display_name_changed_from,
event.root.sender, prevEventContent?.displayName, eventContent?.displayName)
event.root.sender, prevEventContent?.displayName, eventContent?.displayName)
}
displayText.append(displayNameText)
}
@ -72,7 +75,7 @@ class RoomMemberItemFactory(private val stringProvider: StringProvider) {
displayText.append(" ")
stringProvider.getString(R.string.notice_avatar_changed_too)
} 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)
}
@ -80,16 +83,16 @@ class RoomMemberItemFactory(private val stringProvider: StringProvider) {
}
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
return when {
Membership.INVITE == eventContent?.membership -> {
// TODO get userId
val selfUserId: String = ""
val selfUserId = ""
when {
eventContent.thirdPartyInvite != null ->
stringProvider.getString(R.string.notice_room_third_party_registered_invite,
targetDisplayName, eventContent.thirdPartyInvite?.displayName)
targetDisplayName, eventContent.thirdPartyInvite?.displayName)
TextUtils.equals(event.root.stateKey, selfUserId) ->
stringProvider.getString(R.string.notice_room_invite_you, senderDisplayName)
event.root.stateKey.isNullOrEmpty() ->
@ -106,7 +109,8 @@ class RoomMemberItemFactory(private val stringProvider: StringProvider) {
if (prevEventContent?.membership == Membership.INVITE) {
stringProvider.getString(R.string.notice_room_reject, senderDisplayName)
} 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) {
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? {
val content: RoomNameContent? = event.root.content.toModel()
val roomMember = event.roomMember
if (content == null || roomMember == null) {
return null
}
val content: RoomNameContent = event.root.content.toModel() ?: return null
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 {
stringProvider.getString(R.string.notice_room_name_removed, roomMember.displayName)
stringProvider.getString(R.string.notice_room_name_removed, event.senderName)
}
return NoticeItem_()
.noticeText(text)
.avatarUrl(roomMember.avatarUrl)
.memberName(roomMember.displayName)
.avatarUrl(event.senderAvatar)
.memberName(event.senderName)
}

View File

@ -28,20 +28,16 @@ class RoomTopicItemFactory(private val stringProvider: StringProvider) {
fun create(event: TimelineEvent): NoticeItem? {
val content: RoomTopicContent? = event.root.content.toModel()
val roomMember = event.roomMember
if (content == null || roomMember == null) {
return null
}
val content: RoomTopicContent = event.root.content.toModel() ?: return null
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 {
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_()
.noticeText(text)
.avatarUrl(roomMember.avatarUrl)
.memberName(roomMember.displayName)
.avatarUrl(event.senderAvatar)
.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.
// 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.
override fun onScrolled(view: RecyclerView, dx: Int, dy: Int) {
val lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition()
val firstVisibleItemPosition = layoutManager.findFirstVisibleItemPosition()
val totalItemCount = layoutManager.itemCount
// The minimum amount of items to have below your current scroll position
// 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
// We check to see if the dataset count has
// changed, if so we conclude it has finished loading
if (totalItemCount > previousTotalItemCount) {
if (totalItemCount != previousTotalItemCount) {
previousTotalItemCount = totalItemCount
loadingBackwards = false
loadingForwards = false
previousTotalItemCount = totalItemCount
}
// If it isnt currently loading, we check to see if we have reached
// 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
}
}
}