From 8972319a85672f2608019c76c5f9f43562241a17 Mon Sep 17 00:00:00 2001 From: ganfra Date: Sat, 27 Jun 2020 18:26:14 +0200 Subject: [PATCH] Handle typing through RoomSummaryMapper + fix room summary binding called too many times --- .../api/session/room/model/RoomSummary.kt | 2 + .../api/session/typing/TypingUsersTracker.kt | 5 -- .../database/mapper/RoomSummaryMapper.kt | 7 ++- .../internal/session/sync/job/SyncThread.kt | 4 -- .../typing/DefaultTypingUsersTracker.kt | 20 -------- .../room/breadcrumbs/BreadcrumbsController.kt | 4 +- .../home/room/breadcrumbs/BreadcrumbsItem.kt | 15 +++--- .../home/room/detail/RoomDetailViewModel.kt | 18 ++----- .../home/room/list/RoomSummaryItem.kt | 27 +++++----- .../home/room/list/RoomSummaryItemFactory.kt | 5 +- .../features/home/room/typing/TypingHelper.kt | 50 ++++++------------- 11 files changed, 57 insertions(+), 100 deletions(-) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt index 6f21b9eeae..d26ca2dcc4 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/room/model/RoomSummary.kt @@ -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.session.room.model.tag.RoomTag 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 /** @@ -47,6 +48,7 @@ data class RoomSummary constructor( val userDrafts: List = emptyList(), val isEncrypted: Boolean, val encryptionEventTs: Long?, + val typingUsers: List, val inviterId: String? = null, val breadcrumbsIndex: Int = NOT_IN_BREADCRUMBS, val roomEncryptionTrustLevel: RoomEncryptionTrustLevel? = null diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/typing/TypingUsersTracker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/typing/TypingUsersTracker.kt index 642541fd2f..c85b3354d9 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/typing/TypingUsersTracker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/api/session/typing/TypingUsersTracker.kt @@ -16,7 +16,6 @@ package im.vector.matrix.android.api.session.typing -import androidx.lifecycle.LiveData import im.vector.matrix.android.api.session.room.sender.SenderInfo /** @@ -30,8 +29,4 @@ interface TypingUsersTracker { */ fun getTypingUsers(roomId: String): List - /** - * Returns a LiveData of the sender information of all currently typing users in a room, excluding yourself. - */ - fun getTypingUsersLive(roomId: String): LiveData> } diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt index d7bc0a17ae..d266570432 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/database/mapper/RoomSummaryMapper.kt @@ -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.tag.RoomTag import im.vector.matrix.android.internal.database.model.RoomSummaryEntity +import im.vector.matrix.android.internal.session.typing.DefaultTypingUsersTracker 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 { val tags = roomSummaryEntity.tags.map { @@ -31,6 +33,8 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa val latestEvent = roomSummaryEntity.latestPreviewableEvent?.let { 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( roomId = roomSummaryEntity.roomId, @@ -46,6 +50,7 @@ internal class RoomSummaryMapper @Inject constructor(private val timelineEventMa notificationCount = roomSummaryEntity.notificationCount, hasUnreadMessages = roomSummaryEntity.hasUnreadMessages, tags = tags, + typingUsers = typingUsers, membership = roomSummaryEntity.membership, versioningState = roomSummaryEntity.versioningState, readMarkerId = roomSummaryEntity.readMarkerId, diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt index b1dc9c07f1..d17294a967 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/sync/job/SyncThread.kt @@ -202,10 +202,6 @@ internal class SyncThread @Inject constructor(private val syncTask: SyncTask, return } state = newState - // We clear typing users if the sync is not running - if (newState !is SyncState.Running) { - typingUsersTracker.clear() - } debouncer.debounce("post_state", Runnable { liveState.value = newState }, 150) diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/typing/DefaultTypingUsersTracker.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/typing/DefaultTypingUsersTracker.kt index 1aab39b412..a2df64e910 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/typing/DefaultTypingUsersTracker.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/typing/DefaultTypingUsersTracker.kt @@ -16,8 +16,6 @@ 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.typing.TypingUsersTracker import im.vector.matrix.android.internal.session.SessionScope @@ -27,7 +25,6 @@ import javax.inject.Inject internal class DefaultTypingUsersTracker @Inject constructor() : TypingUsersTracker { private val typingUsers = mutableMapOf>() - private val typingUsersLiveData = mutableMapOf>>() /** * 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 if (hasNewValue) { 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 { return typingUsers[roomId] ?: emptyList() } - - override fun getTypingUsersLive(roomId: String): LiveData> { - return typingUsersLiveData.getOrPut(roomId) { - MutableLiveData(getTypingUsers(roomId)) - } - } } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt index 3a7dfd0033..df0f0845b5 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsController.kt @@ -22,11 +22,9 @@ import im.vector.matrix.android.api.util.toMatrixItem import im.vector.riotx.core.epoxy.zeroItem import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.features.home.AvatarRenderer -import im.vector.riotx.features.home.room.typing.TypingHelper import javax.inject.Inject class BreadcrumbsController @Inject constructor( - private val typingHelper: TypingHelper, private val avatarRenderer: AvatarRenderer ) : EpoxyController() { @@ -59,12 +57,12 @@ class BreadcrumbsController @Inject constructor( ?.forEach { breadcrumbsItem { id(it.roomId) + hasTypingUsers(it.typingUsers.isNotEmpty()) avatarRenderer(avatarRenderer) matrixItem(it.toMatrixItem()) unreadNotificationCount(it.notificationCount) showHighlighted(it.highlightCount > 0) hasUnreadMessage(it.hasUnreadMessages) - typingHelper(typingHelper) hasDraft(it.userDrafts.isNotEmpty()) itemClickListener( DebouncedClickListener(View.OnClickListener { _ -> diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt index ef75ac8de9..3e97c6103c 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/breadcrumbs/BreadcrumbsItem.kt @@ -26,22 +26,20 @@ import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.core.epoxy.VectorEpoxyHolder 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.room.list.UnreadCounterBadgeView -import im.vector.riotx.features.home.room.typing.TypingHelper @EpoxyModelClass(layout = R.layout.item_breadcrumbs) abstract class BreadcrumbsItem : VectorEpoxyModel() { - @EpoxyAttribute lateinit var typingHelper: TypingHelper + @EpoxyAttribute var hasTypingUsers: Boolean = false @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @EpoxyAttribute lateinit var matrixItem: MatrixItem @EpoxyAttribute var unreadNotificationCount: Int = 0 @EpoxyAttribute var showHighlighted: Boolean = false @EpoxyAttribute var hasUnreadMessage: 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) { super.bind(holder) @@ -50,9 +48,12 @@ abstract class BreadcrumbsItem : VectorEpoxyModel() { avatarRenderer.render(matrixItem, holder.avatarImageView) holder.unreadCounterBadgeView.render(UnreadCounterBadgeView.State(unreadNotificationCount, showHighlighted)) 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() { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt index 3e8949e94c..941dfb370a 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailViewModel.kt @@ -58,7 +58,6 @@ import im.vector.matrix.android.api.session.room.timeline.getTextEditableContent import im.vector.matrix.android.api.util.toOptional import im.vector.matrix.android.internal.crypto.attachments.toElementToDecrypt 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.unwrap import im.vector.riotx.R @@ -97,12 +96,12 @@ class RoomDetailViewModel @AssistedInject constructor( userPreferencesProvider: UserPreferencesProvider, private val vectorPreferences: VectorPreferences, private val stringProvider: StringProvider, - private val typingHelper: TypingHelper, private val rainbowGenerator: RainbowGenerator, private val session: Session, private val supportedVerificationMethodsProvider: SupportedVerificationMethodsProvider, private val stickerPickerActionHandler: StickerPickerActionHandler, private val roomSummaryHolder: RoomSummaryHolder, + private val typingHelper: TypingHelper, private val webRtcPeerConnectionManager: WebRtcPeerConnectionManager ) : VectorViewModel(initialState), Timeline.Listener { @@ -166,7 +165,6 @@ class RoomDetailViewModel @AssistedInject constructor( observeSummaryState() getUnreadState() observeSyncState() - observeTypings() observeEventDisplayedActions() observeDrafts() observeUnreadState() @@ -1043,15 +1041,6 @@ class RoomDetailViewModel @AssistedInject constructor( } } - private fun observeTypings() { - typingHelper.getTypingMessage(initialState.roomId) - .asObservable() - .subscribe { - setState { copy(typingMessage = it) } - } - .disposeOnClear() - } - private fun getUnreadState() { Observable .combineLatest, RoomSummary, UnreadState>( @@ -1109,8 +1098,11 @@ class RoomDetailViewModel @AssistedInject constructor( private fun observeSummaryState() { asyncSubscribe(RoomDetailViewState::asyncRoomSummary) { summary -> - roomSummaryHolder.set(summary) + setState { + val typingMessage = typingHelper.getTypingMessage(summary.typingUsers) + copy(typingMessage = typingMessage) + } if (summary.membership == Membership.INVITE) { summary.inviterId?.let { inviterId -> session.getUser(inviterId) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt index f918b81d3e..04193dba0d 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItem.kt @@ -32,28 +32,28 @@ import im.vector.matrix.android.api.util.MatrixItem import im.vector.riotx.R import im.vector.riotx.core.epoxy.VectorEpoxyHolder 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.features.crypto.util.toImageRes 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) abstract class RoomSummaryItem : VectorEpoxyModel() { - @EpoxyAttribute lateinit var typingHelper: TypingHelper + @EpoxyAttribute lateinit var typingMessage: CharSequence @EpoxyAttribute lateinit var avatarRenderer: AvatarRenderer @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 var encryptionTrustLevel: RoomEncryptionTrustLevel? = null @EpoxyAttribute var unreadNotificationCount: Int = 0 @EpoxyAttribute var hasUnreadMessage: Boolean = false @EpoxyAttribute var hasDraft: Boolean = false @EpoxyAttribute var showHighlighted: Boolean = false - @EpoxyAttribute var itemLongClickListener: View.OnLongClickListener? = null - @EpoxyAttribute var itemClickListener: View.OnClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemLongClickListener: View.OnLongClickListener? = null + @EpoxyAttribute(EpoxyAttribute.Option.DoNotHash) var itemClickListener: View.OnClickListener? = null @EpoxyAttribute var showSelected: Boolean = false override fun bind(holder: Holder) { @@ -73,11 +73,14 @@ abstract class RoomSummaryItem : VectorEpoxyModel() { holder.roomAvatarDecorationImageView.isVisible = encryptionTrustLevel != null holder.roomAvatarDecorationImageView.setImageResource(encryptionTrustLevel.toImageRes()) renderSelection(holder, showSelected) - typingHelper.getTypingMessage(matrixItem.id).observeK(this) { - Timber.v("Observe typing for room ${matrixItem.id}: $it") - holder.typingView.setTextOrHide(it) - holder.lastEventView.isInvisible = holder.typingView.isVisible - } + holder.typingView.setTextOrHide(typingMessage) + 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) { diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt index 1cd798d3a3..d9381b6546 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/list/RoomSummaryItemFactory.kt @@ -30,6 +30,7 @@ import im.vector.riotx.core.utils.DebouncedClickListener import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.room.detail.timeline.format.DisplayableEventFormatter import im.vector.riotx.features.home.room.typing.TypingHelper +import timber.log.Timber import javax.inject.Inject class RoomSummaryItemFactory @Inject constructor(private val displayableEventFormatter: DisplayableEventFormatter, @@ -102,13 +103,15 @@ class RoomSummaryItemFactory @Inject constructor(private val displayableEventFor dateFormatter.formatMessageDay(date) } } + val typingMessage = typingHelper.getTypingMessage(roomSummary.typingUsers) return RoomSummaryItem_() .id(roomSummary.roomId) .avatarRenderer(avatarRenderer) .encryptionTrustLevel(roomSummary.roomEncryptionTrustLevel) .matrixItem(roomSummary.toMatrixItem()) .lastEventTime(latestEventTime) - .typingHelper(typingHelper) + .typingMessage(typingMessage) + .lastEvent(latestFormattedEvent.toString()) .lastFormattedEvent(latestFormattedEvent) .showHighlighted(showHighlighted) .showSelected(showSelected) diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/typing/TypingHelper.kt b/vector/src/main/java/im/vector/riotx/features/home/room/typing/TypingHelper.kt index 0c6960551c..b47a0a471e 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/typing/TypingHelper.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/typing/TypingHelper.kt @@ -16,48 +16,30 @@ package im.vector.riotx.features.home.room.typing -import androidx.lifecycle.LiveData -import androidx.lifecycle.Transformations -import im.vector.matrix.android.api.session.Session +import im.vector.matrix.android.api.session.room.sender.SenderInfo import im.vector.riotx.R import im.vector.riotx.core.resources.StringProvider import javax.inject.Inject -class TypingHelper @Inject constructor( - 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 { - val liveData = session.typingUsersTracker().getTypingUsersLive(roomId) - return Transformations.map(liveData) { - it.isNotEmpty() - } - } +class TypingHelper @Inject constructor(private val stringProvider: StringProvider) { /** * Returns a human readable String of currently typing users in the room (excluding yourself). */ - fun getTypingMessage(roomId: String): LiveData { - val liveData = session.typingUsersTracker().getTypingUsersLive(roomId) - return Transformations.map(liveData) { typingUsers -> - when { - typingUsers.isEmpty() -> - "" - typingUsers.size == 1 -> - stringProvider.getString(R.string.room_one_user_is_typing, typingUsers[0].disambiguatedDisplayName) - typingUsers.size == 2 -> - stringProvider.getString(R.string.room_two_users_are_typing, - typingUsers[0].disambiguatedDisplayName, - typingUsers[1].disambiguatedDisplayName) - else -> - stringProvider.getString(R.string.room_many_users_are_typing, - typingUsers[0].disambiguatedDisplayName, - typingUsers[1].disambiguatedDisplayName) - } + fun getTypingMessage(typingUsers: List): String { + return when { + typingUsers.isEmpty() -> + "" + typingUsers.size == 1 -> + stringProvider.getString(R.string.room_one_user_is_typing, typingUsers[0].disambiguatedDisplayName) + typingUsers.size == 2 -> + stringProvider.getString(R.string.room_two_users_are_typing, + typingUsers[0].disambiguatedDisplayName, + typingUsers[1].disambiguatedDisplayName) + else -> + stringProvider.getString(R.string.room_many_users_are_typing, + typingUsers[0].disambiguatedDisplayName, + typingUsers[1].disambiguatedDisplayName) } } }