From f09bf61750e934fe5bcd1320d15e1a31f187cfb7 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 7 Jan 2020 13:31:34 +0100 Subject: [PATCH] Room detail: try to get some better perfs with fetching data. LiveData is slow as we only use one HandlerThread at the time. Might want Realm 7.0 and frozen objects to rework that --- .../main/java/im/vector/matrix/rx/RxRoom.kt | 3 + .../session/room/timeline/DefaultTimeline.kt | 16 ++-- .../home/room/detail/RoomDetailFragment.kt | 20 ++-- .../home/room/detail/RoomDetailViewModel.kt | 1 + .../action/MessageActionsEpoxyController.kt | 2 +- .../action/MessageActionsViewModel.kt | 94 +++++++++---------- 6 files changed, 64 insertions(+), 72 deletions(-) diff --git a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt index bf4e924cf0..8a94fa30f7 100644 --- a/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/im/vector/matrix/rx/RxRoom.kt @@ -22,6 +22,7 @@ import im.vector.matrix.android.api.session.room.notification.RoomNotificationSt import im.vector.matrix.android.api.session.room.send.UserDraft import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.util.Optional +import im.vector.matrix.android.api.util.toOptional import io.reactivex.Observable import io.reactivex.Single @@ -29,6 +30,7 @@ class RxRoom(private val room: Room) { fun liveRoomSummary(): Observable> { return room.getRoomSummaryLive().asObservable() + .startWith(room.roomSummary().toOptional()) } fun liveRoomMembers(memberships: List): Observable> { @@ -41,6 +43,7 @@ class RxRoom(private val room: Room) { fun liveTimelineEvent(eventId: String): Observable> { return room.getTimeLineEventLive(eventId).asObservable() + .startWith(room.getTimeLineEvent(eventId).toOptional()) } fun liveReadMarker(): Observable> { diff --git a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt index 7835cf7e3e..3e0f488d1a 100644 --- a/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt +++ b/matrix-sdk-android/src/main/java/im/vector/matrix/android/internal/session/room/timeline/DefaultTimeline.kt @@ -77,7 +77,9 @@ internal class DefaultTimeline( private val hiddenReadReceipts: TimelineHiddenReadReceipts ) : Timeline, TimelineHiddenReadReceipts.Delegate { - val backgroundHandler = createBackgroundHandler("TIMELINE_DB_THREAD_${System.currentTimeMillis()}") + companion object{ + val BACKGROUND_HANDLER = createBackgroundHandler("TIMELINE_DB_THREAD") + } private val listeners = CopyOnWriteArrayList() private val isStarted = AtomicBoolean(false) @@ -135,7 +137,7 @@ internal class DefaultTimeline( // Public methods ****************************************************************************** override fun paginate(direction: Timeline.Direction, count: Int) { - backgroundHandler.post { + BACKGROUND_HANDLER.post { if (!canPaginate(direction)) { return@post } @@ -163,7 +165,7 @@ internal class DefaultTimeline( override fun start() { if (isStarted.compareAndSet(false, true)) { Timber.v("Start timeline for roomId: $roomId and eventId: $initialEventId") - backgroundHandler.post { + BACKGROUND_HANDLER.post { eventDecryptor.start() val realm = Realm.getInstance(realmConfiguration) backgroundRealm.set(realm) @@ -197,8 +199,8 @@ internal class DefaultTimeline( isReady.set(false) Timber.v("Dispose timeline for roomId: $roomId and eventId: $initialEventId") cancelableBag.cancel() - backgroundHandler.removeCallbacksAndMessages(null) - backgroundHandler.post { + BACKGROUND_HANDLER.removeCallbacksAndMessages(null) + BACKGROUND_HANDLER.post { roomEntity?.sendingTimelineEvents?.removeAllChangeListeners() if (this::eventRelations.isInitialized) { eventRelations.removeAllChangeListeners() @@ -514,7 +516,7 @@ internal class DefaultTimeline( } TokenChunkEventPersistor.Result.SHOULD_FETCH_MORE -> // Database won't be updated, so we force pagination request - backgroundHandler.post { + BACKGROUND_HANDLER.post { executePaginationTask(direction, limit) } } @@ -647,7 +649,7 @@ internal class DefaultTimeline( } private fun postSnapshot() { - backgroundHandler.post { + BACKGROUND_HANDLER.post { if (isReady.get().not()) { return@post } diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt index 8c985c27c0..d8502a75b6 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/RoomDetailFragment.kt @@ -40,7 +40,6 @@ import androidx.core.util.Pair import androidx.core.view.ViewCompat import androidx.core.view.forEach import androidx.core.view.isVisible -import androidx.lifecycle.observe import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView @@ -72,7 +71,6 @@ import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent import im.vector.matrix.android.api.util.MatrixItem import im.vector.matrix.android.api.util.toMatrixItem import im.vector.matrix.android.api.util.toRoomAliasMatrixItem -import im.vector.matrix.rx.rx import im.vector.riotx.R import im.vector.riotx.core.dialogs.withColoredButton import im.vector.riotx.core.epoxy.LayoutManagerStateRestorer @@ -89,8 +87,8 @@ import im.vector.riotx.features.attachments.ContactAttachment import im.vector.riotx.features.autocomplete.command.AutocompleteCommandPresenter import im.vector.riotx.features.autocomplete.command.CommandAutocompletePolicy import im.vector.riotx.features.autocomplete.group.AutocompleteGroupPresenter -import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter import im.vector.riotx.features.autocomplete.member.AutocompleteMemberPresenter +import im.vector.riotx.features.autocomplete.room.AutocompleteRoomPresenter import im.vector.riotx.features.command.Command import im.vector.riotx.features.home.AvatarRenderer import im.vector.riotx.features.home.getColorFromUserId @@ -228,10 +226,9 @@ class RoomDetailFragment @Inject constructor( setupJumpToReadMarkerView() setupJumpToBottomView() - roomDetailViewModel.subscribe { renderState(it) } textComposerViewModel.subscribe { renderTextComposerState(it) } - roomDetailViewModel.sendMessageResultLiveData.observeEvent(this) { renderSendMessageResult(it) } + roomDetailViewModel.sendMessageResultLiveData.observeEvent(viewLifecycleOwner) { renderSendMessageResult(it) } roomDetailViewModel.nonBlockingPopAlert.observeEvent(this) { pair -> val message = requireContext().getString(pair.first, *pair.second.toTypedArray()) @@ -345,9 +342,9 @@ class RoomDetailFragment @Inject constructor( AlertDialog.Builder(requireActivity()) .setTitle(R.string.dialog_title_error) .setMessage(getString(R.string.error_file_too_big, - error.filename, - TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes), - TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes) + error.filename, + TextUtils.formatFileSize(requireContext(), error.fileSizeInBytes), + TextUtils.formatFileSize(requireContext(), error.homeServerLimitInBytes) )) .setPositiveButton(R.string.ok, null) .show() @@ -434,7 +431,7 @@ class RoomDetailFragment @Inject constructor( avatarRenderer.render( MatrixItem.UserItem(event.root.senderId - ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar), + ?: "", event.getDisambiguatedDisplayName(), event.senderAvatar), composerLayout.composerRelatedMessageAvatar ) composerLayout.expand { @@ -452,7 +449,7 @@ class RoomDetailFragment @Inject constructor( // Ignore update to avoid saving a draft composerLayout.composerEditText.setText(text) composerLayout.composerEditText.setSelection(composerLayout.composerEditText.text?.length - ?: 0) + ?: 0) } } @@ -793,7 +790,6 @@ class RoomDetailFragment @Inject constructor( } private fun renderState(state: RoomDetailViewState) { - Timber.v("Render state summary complete: ${state.asyncRoomSummary.complete}") renderRoomSummary(state) val summary = state.asyncRoomSummary() val inviter = state.asyncInviter() @@ -1318,7 +1314,7 @@ class RoomDetailFragment @Inject constructor( val startToCompose = composerLayout.composerEditText.text.isNullOrBlank() if (startToCompose - && userId == session.myUserId) { + && userId == session.myUserId) { // Empty composer, current user: start an emote composerLayout.composerEditText.setText(Command.EMOTE.command + " ") composerLayout.composerEditText.setSelection(Command.EMOTE.length) 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 c93358a04e..d4145a7de2 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 @@ -51,6 +51,7 @@ 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.TimelineSettings 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.rx diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt index 939564e780..9a2fb4b6de 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsEpoxyController.kt @@ -93,7 +93,7 @@ class MessageActionsEpoxyController @Inject constructor(private val stringProvid } // Action - state.actions()?.forEachIndexed { index, action -> + state.actions.forEachIndexed { index, action -> if (action is EventSharedAction.Separator) { bottomSheetSeparatorItem { id("separator_$index") diff --git a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt index d537b66ec3..6bf5746735 100644 --- a/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt +++ b/vector/src/main/java/im/vector/riotx/features/home/room/detail/timeline/action/MessageActionsViewModel.kt @@ -31,8 +31,7 @@ import im.vector.matrix.android.api.session.room.send.SendState import im.vector.matrix.android.api.session.room.timeline.TimelineEvent import im.vector.matrix.android.api.session.room.timeline.getLastMessageContent import im.vector.matrix.android.api.session.room.timeline.hasBeenEdited -import im.vector.matrix.android.api.util.Optional -import im.vector.matrix.rx.RxRoom +import im.vector.matrix.rx.rx import im.vector.matrix.rx.unwrap import im.vector.riotx.R import im.vector.riotx.core.extensions.canReact @@ -62,7 +61,7 @@ data class MessageActionState( // For quick reactions val quickStates: Async> = Uninitialized, // For actions - val actions: Async> = Uninitialized, + val actions: List = emptyList(), val expendedReportContentMenu: Boolean = false ) : MvRxState { @@ -112,7 +111,7 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted init { observeEvent() observeReactions() - observeEventAction() + observeTimelineEventState() } override fun handle(action: MessageActionsAction) { @@ -131,32 +130,17 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted private fun observeEvent() { if (room == null) return - RxRoom(room) + room.rx() .liveTimelineEvent(eventId) .unwrap() .execute { - copy( - timelineEvent = it, - messageBody = computeMessageBody(it) - ) - } - } - - private fun observeEventAction() { - if (room == null) return - RxRoom(room) - .liveTimelineEvent(eventId) - .map { - actionsForEvent(it) - } - .execute { - copy(actions = it) + copy(timelineEvent = it) } } private fun observeReactions() { if (room == null) return - RxRoom(room) + room.rx() .liveAnnotationSummary(eventId) .map { annotations -> quickEmojis.map { emoji -> @@ -168,11 +152,19 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted } } - private fun computeMessageBody(timelineEvent: Async): CharSequence? { - return when (timelineEvent()?.root?.getClearType()) { + private fun observeTimelineEventState() { + asyncSubscribe(MessageActionState::timelineEvent) { timelineEvent -> + val computedMessage = computeMessageBody(timelineEvent) + val actions = actionsForEvent(timelineEvent) + setState { copy(messageBody = computedMessage, actions = actions) } + } + } + + private fun computeMessageBody(timelineEvent: TimelineEvent): CharSequence? { + return when (timelineEvent.root.getClearType()) { EventType.MESSAGE, EventType.STICKER -> { - val messageContent: MessageContent? = timelineEvent()?.getLastMessageContent() + val messageContent: MessageContent? = timelineEvent.getLastMessageContent() if (messageContent is MessageTextContent && messageContent.format == MessageType.FORMAT_MATRIX_HTML) { val html = messageContent.formattedBody ?.takeIf { it.isNotBlank() } @@ -193,41 +185,39 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted EventType.CALL_INVITE, EventType.CALL_HANGUP, EventType.CALL_ANSWER -> { - timelineEvent()?.let { noticeEventFormatter.format(it) } + noticeEventFormatter.format(timelineEvent) } else -> null } } - private fun actionsForEvent(optionalEvent: Optional): List { - val event = optionalEvent.getOrNull() ?: return emptyList() - - val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent.toModel() - ?: event.root.getClearContent().toModel() + private fun actionsForEvent(timelineEvent: TimelineEvent): List { + val messageContent: MessageContent? = timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() + ?: timelineEvent.root.getClearContent().toModel() val type = messageContent?.type return arrayListOf().apply { - if (event.root.sendState.hasFailed()) { - if (canRetry(event)) { + if (timelineEvent.root.sendState.hasFailed()) { + if (canRetry(timelineEvent)) { add(EventSharedAction.Resend(eventId)) } add(EventSharedAction.Remove(eventId)) - } else if (event.root.sendState.isSending()) { + } else if (timelineEvent.root.sendState.isSending()) { // TODO is uploading attachment? - if (canCancel(event)) { + if (canCancel(timelineEvent)) { add(EventSharedAction.Cancel(eventId)) } - } else if (event.root.sendState == SendState.SYNCED) { - if (!event.root.isRedacted()) { - if (canReply(event, messageContent)) { + } else if (timelineEvent.root.sendState == SendState.SYNCED) { + if (!timelineEvent.root.isRedacted()) { + if (canReply(timelineEvent, messageContent)) { add(EventSharedAction.Reply(eventId)) } - if (canEdit(event, session.myUserId)) { + if (canEdit(timelineEvent, session.myUserId)) { add(EventSharedAction.Edit(eventId)) } - if (canRedact(event, session.myUserId)) { + if (canRedact(timelineEvent, session.myUserId)) { add(EventSharedAction.Delete(eventId)) } @@ -236,19 +226,19 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted add(EventSharedAction.Copy(messageContent!!.body)) } - if (event.canReact()) { + if (timelineEvent.canReact()) { add(EventSharedAction.AddReaction(eventId)) } - if (canQuote(event, messageContent)) { + if (canQuote(timelineEvent, messageContent)) { add(EventSharedAction.Quote(eventId)) } - if (canViewReactions(event)) { + if (canViewReactions(timelineEvent)) { add(EventSharedAction.ViewReactions(informationData)) } - if (event.hasBeenEdited()) { + if (timelineEvent.hasBeenEdited()) { add(EventSharedAction.ViewEditHistory(informationData)) } @@ -261,29 +251,29 @@ class MessageActionsViewModel @AssistedInject constructor(@Assisted // TODO } - if (event.root.sendState == SendState.SENT) { + if (timelineEvent.root.sendState == SendState.SENT) { // TODO Can be redacted // TODO sent by me or sufficient power level } } - add(EventSharedAction.ViewSource(event.root.toContentStringWithIndent())) - if (event.isEncrypted()) { - val decryptedContent = event.root.toClearContentStringWithIndent() + add(EventSharedAction.ViewSource(timelineEvent.root.toContentStringWithIndent())) + if (timelineEvent.isEncrypted()) { + val decryptedContent = timelineEvent.root.toClearContentStringWithIndent() ?: stringProvider.getString(R.string.encryption_information_decryption_error) add(EventSharedAction.ViewDecryptedSource(decryptedContent)) } add(EventSharedAction.CopyPermalink(eventId)) - if (session.myUserId != event.root.senderId) { + if (session.myUserId != timelineEvent.root.senderId) { // not sent by me - if (event.root.getClearType() == EventType.MESSAGE) { - add(EventSharedAction.ReportContent(eventId, event.root.senderId)) + if (timelineEvent.root.getClearType() == EventType.MESSAGE) { + add(EventSharedAction.ReportContent(eventId, timelineEvent.root.senderId)) } add(EventSharedAction.Separator) - add(EventSharedAction.IgnoreUser(event.root.senderId)) + add(EventSharedAction.IgnoreUser(timelineEvent.root.senderId)) } } }