Merge pull request #1569 from vector-im/feature/fix_improve_epoxy

Feature/fix improve epoxy
This commit is contained in:
Benoit Marty 2020-06-30 17:28:34 +02:00 committed by GitHub
commit a6f4cd74d5
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
19 changed files with 103 additions and 113 deletions

View file

@ -19,6 +19,7 @@ package im.vector.matrix.android.api.session.room.model
import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel import im.vector.matrix.android.api.crypto.RoomEncryptionTrustLevel
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.send.UserDraft import im.vector.matrix.android.api.session.room.send.UserDraft
import im.vector.matrix.android.api.session.room.sender.SenderInfo
import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.TimelineEvent
/** /**
@ -49,6 +50,7 @@ data class RoomSummary constructor(
val userDrafts: List<UserDraft> = emptyList(), val userDrafts: List<UserDraft> = emptyList(),
val isEncrypted: Boolean, val isEncrypted: Boolean,
val encryptionEventTs: Long?, val encryptionEventTs: Long?,
val typingUsers: List<SenderInfo>,
val inviterId: String? = null, val inviterId: String? = null,
val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS, val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS,
val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null

View file

@ -16,7 +16,6 @@
package im.vector.matrix.android.api.session.typing package im.vector.matrix.android.api.session.typing
import androidx.lifecycle.LiveData
import im.vector.matrix.android.api.session.room.sender.SenderInfo import im.vector.matrix.android.api.session.room.sender.SenderInfo
/** /**
@ -29,9 +28,4 @@ interface TypingUsersTracker {
* Returns the sender information of all currently typing users in a room, excluding yourself. * Returns the sender information of all currently typing users in a room, excluding yourself.
*/ */
fun getTypingUsers(roomId: String): List<SenderInfo> fun getTypingUsers(roomId: String): List<SenderInfo>
/**
* Returns a LiveData of the sender information of all currently typing users in a room, excluding yourself.
*/
fun getTypingUsersLive(roomId: String): LiveData<List<SenderInfo>>
} }

View file

@ -19,9 +19,11 @@ 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.typing.DefaultTypingUsersTracker
import javax.inject.Inject import javax.inject.Inject
internal class RoomSummaryMapper @Inject constructor(private val timelineEventMapper: TimelineEventMapper) { internal class RoomSummaryMapper @Inject constructor(private val timelineEventMapper: TimelineEventMapper,
private val typingUsersTracker: DefaultTypingUsersTracker) {
fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary { fun map(roomSummaryEntity: RoomSummaryEntity): RoomSummary {
val tags = roomSummaryEntity.tags.map { val tags = roomSummaryEntity.tags.map {
@ -31,6 +33,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let { val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let {
timelineEventMapper.map(it, buildReadReceipts = false) timelineEventMapper.map(it, buildReadReceipts = false)
} }
// typings are updated through the sync where room summary entity gets updated no matter what, so it's ok get there
val typingUsers = typingUsersTracker.getTypingUsers(roomSummaryEntity.roomId)
return RoomSummary( return RoomSummary(
roomId = roomSummaryEntity.roomId, roomId = roomSummaryEntity.roomId,
@ -47,6 +51,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa
notificationCount = roomSummaryEntity.notificationCount, notificationCount = roomSummaryEntity.notificationCount,
hasUnreadMessages = roomSummaryEntity.hasUnreadMessages, hasUnreadMessages = roomSummaryEntity.hasUnreadMessages,
tags = tags, tags = tags,
typingUsers = typingUsers,
membership = roomSummaryEntity.membership, membership = roomSummaryEntity.membership,
versioningState = roomSummaryEntity.versioningState, versioningState = roomSummaryEntity.versioningState,
readMarkerId = roomSummaryEntity.readMarkerId, readMarkerId = roomSummaryEntity.readMarkerId,

View file

@ -202,10 +202,6 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask,
return return
} }
state = newState state = newState
// We clear typing users if the sync is not running
if (newState !is SyncState.Running) {
typingUsersTracker.clear()
}
debouncer.debounce("post_state", Runnable { debouncer.debounce("post_state", Runnable {
liveState.value = newState liveState.value = newState
}, 150) }, 150)

View file

@ -16,8 +16,6 @@
package im.vector.matrix.android.internal.session.typing package im.vector.matrix.android.internal.session.typing
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import im.vector.matrix.android.api.session.room.sender.SenderInfo import im.vector.matrix.android.api.session.room.sender.SenderInfo
import im.vector.matrix.android.api.session.typing.TypingUsersTracker import im.vector.matrix.android.api.session.typing.TypingUsersTracker
import im.vector.matrix.android.internal.session.SessionScope import im.vector.matrix.android.internal.session.SessionScope
@ -27,7 +25,6 @@ import javax.inject.Inject
internal class DefaultTypingUsersTracker @Inject constructor() : TypingUsersTracker { internal class DefaultTypingUsersTracker @Inject constructor() : TypingUsersTracker {
private val typingUsers = mutableMapOf<String, List<SenderInfo>>() private val typingUsers = mutableMapOf<String, List<SenderInfo>>()
private val typingUsersLiveData = mutableMapOf<String, MutableLiveData<List<SenderInfo>>>()
/** /**
* Set all currently typing users for a room (excluding yourself) * Set all currently typing users for a room (excluding yourself)
@ -36,27 +33,10 @@ internal class DefaultTypingUsersTracker @Inject constructor() : TypingUsersTrac
val hasNewValue = typingUsers[roomId] != senderInfoList val hasNewValue = typingUsers[roomId] != senderInfoList
if (hasNewValue) { if (hasNewValue) {
typingUsers[roomId] = senderInfoList typingUsers[roomId] = senderInfoList
typingUsersLiveData[roomId]?.postValue(senderInfoList)
}
}
/**
* Can be called when there is no sync so you don't get stuck with ephemeral data
*/
fun clear() {
val roomIds = typingUsers.keys
roomIds.forEach {
setTypingUsersFromRoom(it, emptyList())
} }
} }
override fun getTypingUsers(roomId: String): List<SenderInfo> { override fun getTypingUsers(roomId: String): List<SenderInfo> {
return typingUsers[roomId] ?: emptyList() return typingUsers[roomId] ?: emptyList()
} }
override fun getTypingUsersLive(roomId: String): LiveData<List<SenderInfo>> {
return typingUsersLiveData.getOrPut(roomId) {
MutableLiveData(getTypingUsers(roomId))
}
}
} }

View file

@ -22,11 +22,9 @@ import im.vector.matrix.android.api.util.toMatrixItem
import im.vector.riotx.core.epoxy.zeroItem import im.vector.riotx.core.epoxy.zeroItem
import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.core.utils.DebouncedClickListener
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.typing.TypingHelper
import javax.inject.Inject import javax.inject.Inject
class BreadcrumbsController @Inject constructor( class BreadcrumbsController @Inject constructor(
private val typingHelper: TypingHelper,
private val avatarRenderer: AvatarRenderer private val avatarRenderer: AvatarRenderer
) : EpoxyController() { ) : EpoxyController() {
@ -59,12 +57,12 @@ class BreadcrumbsController @Inject constructor(
?.forEach { ?.forEach {
breadcrumbsItem { breadcrumbsItem {
id(it.roomId) id(it.roomId)
hasTypingUsers(it.typingUsers.isNotEmpty())
avatarRenderer(avatarRenderer) avatarRenderer(avatarRenderer)
matrixItem(it.toMatrixItem()) matrixItem(it.toMatrixItem())
unreadNotificationCount(it.notificationCount) unreadNotificationCount(it.notificationCount)
showHighlighted(it.highlightCount > 0) showHighlighted(it.highlightCount > 0)
hasUnreadMessage(it.hasUnreadMessages) hasUnreadMessage(it.hasUnreadMessages)
typingHelper(typingHelper)
hasDraft(it.userDrafts.isNotEmpty()) hasDraft(it.userDrafts.isNotEmpty())
itemClickListener( itemClickListener(
DebouncedClickListener(View.OnClickListener { _ -> DebouncedClickListener(View.OnClickListener { _ ->

View file

@ -26,22 +26,20 @@ import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.extensions.observeNotNull
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.list.UnreadCounterBadgeView import im.vector.riotx.features.home.room.list.UnreadCounterBadgeView
import im.vector.riotx.features.home.room.typing.TypingHelper
@EpoxyModelClass(layout = R.layout.item_breadcrumbs) @EpoxyModelClass(layout = R.layout.item_breadcrumbs)
abstract class BreadcrumbsItem : VectorEpoxyModel<BreadcrumbsItem.Holder>() { abstract class BreadcrumbsItem : VectorEpoxyModel<BreadcrumbsItem.Holder>() {
@EpoxyAttribute lateinit var typingHelper: TypingHelper @EpoxyAttribute var hasTypingUsers: Boolean = false
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem @EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute var unreadNotificationCount: Int = 0 @EpoxyAttribute var unreadNotificationCount: Int = 0
@EpoxyAttribute var showHighlighted: Boolean = false @EpoxyAttribute var showHighlighted: Boolean = false
@EpoxyAttribute var hasUnreadMessage: Boolean = false @EpoxyAttribute var hasUnreadMessage: Boolean = false
@EpoxyAttribute var hasDraft: Boolean = false @EpoxyAttribute var hasDraft: Boolean = false
@EpoxyAttribute var itemClickListener: View.OnClickListener? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
super.bind(holder) super.bind(holder)
@ -50,9 +48,12 @@ abstract class BreadcrumbsItem : VectorEpoxyModel<BreadcrumbsItem.Holder>() {
avatarRenderer.render(matrixItem, holder.avatarImageView) avatarRenderer.render(matrixItem, holder.avatarImageView)
holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted)) holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted))
holder.draftIndentIndicator.isVisible = hasDraft holder.draftIndentIndicator.isVisible = hasDraft
typingHelper.hasTypingUsers(matrixItem.id).observeNotNull(this) { hasTypingUsers -> holder.typingIndicator.isVisible = hasTypingUsers
holder.typingIndicator.isVisible = hasTypingUsers }
}
override fun unbind(holder: Holder) {
holder.rootView.setOnClickListener(null)
super.unbind(holder)
} }
class Holder : VectorEpoxyHolder() { class Holder : VectorEpoxyHolder() {

View file

@ -59,7 +59,6 @@ import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent
import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.android.api.util.toOptional
import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt
import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent import im.vector.matrix.android.internal.crypto.model.event.EncryptedEventContent
import im.vector.matrix.rx.asObservable
import im.vector.matrix.rx.rx import im.vector.matrix.rx.rx
import im.vector.matrix.rx.unwrap import im.vector.matrix.rx.unwrap
import im.vector.riotx.R import im.vector.riotx.R
@ -98,12 +97,12 @@ class RoomDetailViewModel @AssistedInject constructor(
userPreferencesProvider: UserPreferencesProvider, userPreferencesProvider: UserPreferencesProvider,
private val vectorPreferences: VectorPreferences, private val vectorPreferences: VectorPreferences,
private val stringProvider: StringProvider, private val stringProvider: StringProvider,
private val typingHelper: TypingHelper,
private val rainbowGenerator: RainbowGenerator, private val rainbowGenerator: RainbowGenerator,
private val session: Session, private val session: Session,
private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider, private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider,
private val stickerPickerActionHandler: StickerPickerActionHandler, private val stickerPickerActionHandler: StickerPickerActionHandler,
private val roomSummaryHolder: RoomSummaryHolder, private val roomSummaryHolder: RoomSummaryHolder,
private val typingHelper: TypingHelper,
private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager
) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), Timeline.Listener { ) : VectorViewModel<RoomDetailViewState, RoomDetailAction, RoomDetailViewEvents>(initialState), Timeline.Listener {
@ -167,7 +166,6 @@ class RoomDetailViewModel @AssistedInject constructor(
observeSummaryState() observeSummaryState()
getUnreadState() getUnreadState()
observeSyncState() observeSyncState()
observeTypings()
observeEventDisplayedActions() observeEventDisplayedActions()
observeDrafts() observeDrafts()
observeUnreadState() observeUnreadState()
@ -1061,15 +1059,6 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
} }
private fun observeTypings() {
typingHelper.getTypingMessage(initialState.roomId)
.asObservable()
.subscribe {
setState { copy(typingMessage = it) }
}
.disposeOnClear()
}
private fun getUnreadState() { private fun getUnreadState() {
Observable Observable
.combineLatest<List<TimelineEvent>, RoomSummary, UnreadState>( .combineLatest<List<TimelineEvent>, RoomSummary, UnreadState>(
@ -1127,8 +1116,11 @@ class RoomDetailViewModel @AssistedInject constructor(
private fun observeSummaryState() { private fun observeSummaryState() {
asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary -> asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary ->
roomSummaryHolder.set(summary) roomSummaryHolder.set(summary)
setState {
val typingMessage = typingHelper.getTypingMessage(summary.typingUsers)
copy(typingMessage = typingMessage)
}
if (summary.membership == Membership.INVITE) { if (summary.membership == Membership.INVITE) {
summary.inviterId?.let { inviterId -> summary.inviterId?.let { inviterId ->
session.getUser(inviterId) session.getUser(inviterId)

View file

@ -20,6 +20,7 @@ import androidx.annotation.ColorInt
import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.send.SendState
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.getColorFromUserId
import im.vector.riotx.features.settings.VectorPreferences import im.vector.riotx.features.settings.VectorPreferences
import javax.inject.Inject import javax.inject.Inject
@ -27,6 +28,11 @@ class MessageColorProvider @Inject constructor(
private val colorProvider: ColorProvider, private val colorProvider: ColorProvider,
private val vectorPreferences: VectorPreferences) { private val vectorPreferences: VectorPreferences) {
@ColorInt
fun getMemberNameTextColor(userId: String): Int {
return colorProvider.getColor(getColorFromUserId(userId))
}
@ColorInt @ColorInt
fun getMessageTextColor(sendState: SendState): Int { fun getMessageTextColor(sendState: SendState): Int {
return if (vectorPreferences.developerMode()) { return if (vectorPreferences.developerMode()) {

View file

@ -33,14 +33,12 @@ import im.vector.matrix.android.internal.session.room.VerificationState
import im.vector.riotx.core.date.VectorDateFormatter import im.vector.riotx.core.date.VectorDateFormatter
import im.vector.riotx.core.extensions.localDateTime import im.vector.riotx.core.extensions.localDateTime
import im.vector.riotx.core.resources.ColorProvider import im.vector.riotx.core.resources.ColorProvider
import im.vector.riotx.core.utils.getColorFromUserId
import im.vector.riotx.features.home.room.detail.timeline.item.E2EDecoration import im.vector.riotx.features.home.room.detail.timeline.item.E2EDecoration
import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData import im.vector.riotx.features.home.room.detail.timeline.item.MessageInformationData
import im.vector.riotx.features.home.room.detail.timeline.item.PollResponseData import im.vector.riotx.features.home.room.detail.timeline.item.PollResponseData
import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData import im.vector.riotx.features.home.room.detail.timeline.item.ReactionInfoData
import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData import im.vector.riotx.features.home.room.detail.timeline.item.ReadReceiptData
import im.vector.riotx.features.home.room.detail.timeline.item.ReferencesInfoData import im.vector.riotx.features.home.room.detail.timeline.item.ReferencesInfoData
import me.gujun.android.span.span
import javax.inject.Inject import javax.inject.Inject
/** /**
@ -71,11 +69,8 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
|| isTileTypeMessage(nextEvent) || isTileTypeMessage(nextEvent)
val time = dateFormatter.formatMessageHour(date) val time = dateFormatter.formatMessageHour(date)
val formattedMemberName = span(event.senderInfo.disambiguatedDisplayName) {
textColor = colorProvider.getColor(getColorFromUserId(event.root.senderId))
}
val e2eDecoration = getE2EDecoration(event) val e2eDecoration = getE2EDecoration(event)
return MessageInformationData( return MessageInformationData(
eventId = eventId, eventId = eventId,
senderId = event.root.senderId ?: "", senderId = event.root.senderId ?: "",
@ -83,7 +78,7 @@ class MessageInformationDataFactory @Inject constructor(private val session: Ses
time = time, time = time,
ageLocalTS = event.root.ageLocalTs, ageLocalTS = event.root.ageLocalTs,
avatarUrl = event.senderInfo.avatarUrl, avatarUrl = event.senderInfo.avatarUrl,
memberName = formattedMemberName, memberName = event.senderInfo.disambiguatedDisplayName,
showInformation = showInformation, showInformation = showInformation,
orderedReactionList = event.annotations?.reactionsSummary orderedReactionList = event.annotations?.reactionsSummary
// ?.filter { isSingleEmoji(it.key) } // ?.filter { isSingleEmoji(it.key) }

View file

@ -109,6 +109,7 @@ abstract class AbsBaseMessageItem<H : AbsBaseMessageItem.Holder> : BaseEventItem
} }
override fun unbind(holder: H) { override fun unbind(holder: H) {
holder.reactionsContainer.setOnLongClickListener(null)
holder.readReceiptsView.unbind() holder.readReceiptsView.unbind()
super.unbind(holder) super.unbind(holder)
} }

View file

@ -61,6 +61,7 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
holder.timeView.visibility = View.VISIBLE holder.timeView.visibility = View.VISIBLE
holder.timeView.text = attributes.informationData.time holder.timeView.text = attributes.informationData.time
holder.memberNameView.text = attributes.informationData.memberName holder.memberNameView.text = attributes.informationData.memberName
holder.memberNameView.setTextColor(attributes.getMemberNameColor())
attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView) attributes.avatarRenderer.render(attributes.informationData.matrixItem, holder.avatarImageView)
holder.avatarImageView.setOnLongClickListener(attributes.itemLongClickListener) holder.avatarImageView.setOnLongClickListener(attributes.itemLongClickListener)
holder.memberNameView.setOnLongClickListener(attributes.itemLongClickListener) holder.memberNameView.setOnLongClickListener(attributes.itemLongClickListener)
@ -75,6 +76,16 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
} }
} }
override fun unbind(holder: H) {
holder.avatarImageView.setOnClickListener(null)
holder.avatarImageView.setOnLongClickListener(null)
holder.memberNameView.setOnClickListener(null)
holder.memberNameView.setOnLongClickListener(null)
super.unbind(holder)
}
private fun Attributes.getMemberNameColor() = messageColorProvider.getMemberNameTextColor(informationData.senderId)
abstract class Holder(@IdRes stubId: Int) : AbsBaseMessageItem.Holder(stubId) { abstract class Holder(@IdRes stubId: Int) : AbsBaseMessageItem.Holder(stubId) {
val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView) val avatarImageView by bind<ImageView>(R.id.messageAvatarImageView)
val memberNameView by bind<TextView>(R.id.messageMemberNameView) val memberNameView by bind<TextView>(R.id.messageMemberNameView)
@ -96,5 +107,25 @@ abstract class AbsMessageItem<H : AbsMessageItem.Holder> : AbsBaseMessageItem<H>
val avatarCallback: TimelineEventController.AvatarCallback? = null, val avatarCallback: TimelineEventController.AvatarCallback? = null,
override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null, override val readReceiptsCallback: TimelineEventController.ReadReceiptsCallback? = null,
val emojiTypeFace: Typeface? = null val emojiTypeFace: Typeface? = null
) : AbsBaseMessageItem.Attributes ) : AbsBaseMessageItem.Attributes {
// Have to override as it's used to diff epoxy items
override fun equals(other: Any?): Boolean {
if (this === other) return true
if (javaClass != other?.javaClass) return false
other as Attributes
if (avatarSize != other.avatarSize) return false
if (informationData != other.informationData) return false
return true
}
override fun hashCode(): Int {
var result = avatarSize
result = 31 * result + informationData.hashCode()
return result
}
}
} }

View file

@ -40,7 +40,7 @@ abstract class BaseEventItem<H : BaseEventItem.BaseHolder> : VectorEpoxyModel<H>
@EpoxyAttribute @EpoxyAttribute
open var leftGuideline: Int = 0 open var leftGuideline: Int = 0
@EpoxyAttribute @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
lateinit var dimensionConverter: DimensionConverter lateinit var dimensionConverter: DimensionConverter
@CallSuper @CallSuper

View file

@ -36,7 +36,7 @@ abstract class MessageFileItem : AbsMessageItem<MessageFileItem.Holder>() {
@EpoxyAttribute @EpoxyAttribute
@DrawableRes @DrawableRes
var iconRes: Int = 0 var iconRes: Int = 0
@EpoxyAttribute @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var clickListener: View.OnClickListener? = null var clickListener: View.OnClickListener? = null
@EpoxyAttribute @EpoxyAttribute
var izLocalFile = false var izLocalFile = false

View file

@ -37,7 +37,7 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
var playable: Boolean = false var playable: Boolean = false
@EpoxyAttribute @EpoxyAttribute
var mode = ImageContentRenderer.Mode.THUMBNAIL var mode = ImageContentRenderer.Mode.THUMBNAIL
@EpoxyAttribute @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var clickListener: View.OnClickListener? = null var clickListener: View.OnClickListener? = null
@EpoxyAttribute @EpoxyAttribute
lateinit var imageContentRenderer: ImageContentRenderer lateinit var imageContentRenderer: ImageContentRenderer
@ -65,6 +65,8 @@ abstract class MessageImageVideoItem : AbsMessageItem<MessageImageVideoItem.Hold
override fun unbind(holder: Holder) { override fun unbind(holder: Holder) {
GlideApp.with(holder.view.context.applicationContext).clear(holder.imageView) GlideApp.with(holder.view.context.applicationContext).clear(holder.imageView)
contentUploadStateTrackerBinder.unbind(attributes.informationData.eventId) contentUploadStateTrackerBinder.unbind(attributes.informationData.eventId)
holder.imageView.setOnClickListener(null)
holder.imageView.setOnLongClickListener(null)
super.unbind(holder) super.unbind(holder)
} }

View file

@ -34,7 +34,7 @@ abstract class MessageTextItem : AbsMessageItem<MessageTextItem.Holder>() {
var message: CharSequence? = null var message: CharSequence? = null
@EpoxyAttribute @EpoxyAttribute
var useBigFont: Boolean = false var useBigFont: Boolean = false
@EpoxyAttribute @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash)
var movementMethod: MovementMethod? = null var movementMethod: MovementMethod? = null
override fun bind(holder: Holder) { override fun bind(holder: Holder) {

View file

@ -32,28 +32,28 @@ import im.vector.matrix.android.api.util.MatrixItem
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.epoxy.VectorEpoxyHolder import im.vector.riotx.core.epoxy.VectorEpoxyHolder
import im.vector.riotx.core.epoxy.VectorEpoxyModel import im.vector.riotx.core.epoxy.VectorEpoxyModel
import im.vector.riotx.core.extensions.observeK
import im.vector.riotx.core.extensions.setTextOrHide import im.vector.riotx.core.extensions.setTextOrHide
import im.vector.riotx.features.crypto.util.toImageRes import im.vector.riotx.features.crypto.util.toImageRes
import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.AvatarRenderer
import im.vector.riotx.features.home.room.typing.TypingHelper
import timber.log.Timber
@EpoxyModelClass(layout = R.layout.item_room) @EpoxyModelClass(layout = R.layout.item_room)
abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() { abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
@EpoxyAttribute lateinit var typingHelper: TypingHelper @EpoxyAttribute lateinit var typingMessage: CharSequence
@EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer
@EpoxyAttribute lateinit var matrixItem: MatrixItem @EpoxyAttribute lateinit var matrixItem: MatrixItem
@EpoxyAttribute lateinit var lastFormattedEvent: CharSequence // Used only for diff calculation
@EpoxyAttribute lateinit var lastEvent: String
// We use DoNotHash here as Spans are not implementing equals/hashcode
@EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) lateinit var lastFormattedEvent: CharSequence
@EpoxyAttribute lateinit var lastEventTime: CharSequence @EpoxyAttribute lateinit var lastEventTime: CharSequence
@EpoxyAttribute var encryptionTrustLevel: RoomEncryptionTrustLevel? = null @EpoxyAttribute var encryptionTrustLevel: RoomEncryptionTrustLevel? = null
@EpoxyAttribute var unreadNotificationCount: Int = 0 @EpoxyAttribute var unreadNotificationCount: Int = 0
@EpoxyAttribute var hasUnreadMessage: Boolean = false @EpoxyAttribute var hasUnreadMessage: Boolean = false
@EpoxyAttribute var hasDraft: Boolean = false @EpoxyAttribute var hasDraft: Boolean = false
@EpoxyAttribute var showHighlighted: Boolean = false @EpoxyAttribute var showHighlighted: Boolean = false
@EpoxyAttribute var itemLongClickListener: View.OnLongClickListener? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemLongClickListener: View.OnLongClickListener? = null
@EpoxyAttribute var itemClickListener: View.OnClickListener? = null @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null
@EpoxyAttribute var showSelected: Boolean = false @EpoxyAttribute var showSelected: Boolean = false
override fun bind(holder: Holder) { override fun bind(holder: Holder) {
@ -73,11 +73,14 @@ abstract class RoomSummaryItem : VectorEpoxyModel<RoomSummaryItem.Holder>() {
holder.roomAvatarDecorationImageView.isVisible = encryptionTrustLevel != null holder.roomAvatarDecorationImageView.isVisible = encryptionTrustLevel != null
holder.roomAvatarDecorationImageView.setImageResource(encryptionTrustLevel.toImageRes()) holder.roomAvatarDecorationImageView.setImageResource(encryptionTrustLevel.toImageRes())
renderSelection(holder, showSelected) renderSelection(holder, showSelected)
typingHelper.getTypingMessage(matrixItem.id).observeK(this) { holder.typingView.setTextOrHide(typingMessage)
Timber.v("Observe typing for room ${matrixItem.id}: $it") holder.lastEventView.isInvisible = holder.typingView.isVisible
holder.typingView.setTextOrHide(it) }
holder.lastEventView.isInvisible = holder.typingView.isVisible
} override fun unbind(holder: Holder) {
holder.rootView.setOnClickListener(null)
holder.rootView.setOnLongClickListener(null)
super.unbind(holder)
} }
private fun renderSelection(holder: Holder, isSelected: Boolean) { private fun renderSelection(holder: Holder, isSelected: Boolean) {

View file

@ -102,13 +102,15 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor
dateFormatter.formatMessageDay(date) dateFormatter.formatMessageDay(date)
} }
} }
val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers)
return RoomSummaryItem_() return RoomSummaryItem_()
.id(roomSummary.roomId) .id(roomSummary.roomId)
.avatarRenderer(avatarRenderer) .avatarRenderer(avatarRenderer)
.encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel) .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel)
.matrixItem(roomSummary.toMatrixItem()) .matrixItem(roomSummary.toMatrixItem())
.lastEventTime(latestEventTime) .lastEventTime(latestEventTime)
.typingHelper(typingHelper) .typingMessage(typingMessage)
.lastEvent(latestFormattedEvent.toString())
.lastFormattedEvent(latestFormattedEvent) .lastFormattedEvent(latestFormattedEvent)
.showHighlighted(showHighlighted) .showHighlighted(showHighlighted)
.showSelected(showSelected) .showSelected(showSelected)

View file

@ -16,48 +16,30 @@
package im.vector.riotx.features.home.room.typing package im.vector.riotx.features.home.room.typing
import androidx.lifecycle.LiveData import im.vector.matrix.android.api.session.room.sender.SenderInfo
import androidx.lifecycle.Transformations
import im.vector.matrix.android.api.session.Session
import im.vector.riotx.R import im.vector.riotx.R
import im.vector.riotx.core.resources.StringProvider import im.vector.riotx.core.resources.StringProvider
import javax.inject.Inject import javax.inject.Inject
class TypingHelper @Inject constructor( class TypingHelper @Inject constructor(private val stringProvider: StringProvider) {
private val session: Session,
private val stringProvider: StringProvider
) {
/**
* Return true if some users are currently typing in the room (excluding yourself).
*/
fun hasTypingUsers(roomId: String): LiveData<Boolean> {
val liveData = session.typingUsersTracker().getTypingUsersLive(roomId)
return Transformations.map(liveData) {
it.isNotEmpty()
}
}
/** /**
* Returns a human readable String of currently typing users in the room (excluding yourself). * Returns a human readable String of currently typing users in the room (excluding yourself).
*/ */
fun getTypingMessage(roomId: String): LiveData<String> { fun getTypingMessage(typingUsers: List<SenderInfo>): String {
val liveData = session.typingUsersTracker().getTypingUsersLive(roomId) return when {
return Transformations.map(liveData) { typingUsers -> typingUsers.isEmpty() ->
when { ""
typingUsers.isEmpty() -> typingUsers.size == 1 ->
"" stringProvider.getString(R.string.room_one_user_is_typing, typingUsers[0].disambiguatedDisplayName)
typingUsers.size == 1 -> typingUsers.size == 2 ->
stringProvider.getString(R.string.room_one_user_is_typing, typingUsers[0].disambiguatedDisplayName) stringProvider.getString(R.string.room_two_users_are_typing,
typingUsers.size == 2 -> typingUsers[0].disambiguatedDisplayName,
stringProvider.getString(R.string.room_two_users_are_typing, typingUsers[1].disambiguatedDisplayName)
typingUsers[0].disambiguatedDisplayName, else ->
typingUsers[1].disambiguatedDisplayName) stringProvider.getString(R.string.room_many_users_are_typing,
else -> typingUsers[0].disambiguatedDisplayName,
stringProvider.getString(R.string.room_many_users_are_typing, typingUsers[1].disambiguatedDisplayName)
typingUsers[0].disambiguatedDisplayName,
typingUsers[1].disambiguatedDisplayName)
}
} }
} }
} }