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

This commit is contained in:
ganfra 2020-01-07 13:31:34 +01:00
parent f9487f8995
commit f09bf61750
6 changed files with 64 additions and 72 deletions

View File

@ -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<Optional<RoomSummary>> {
return room.getRoomSummaryLive().asObservable()
.startWith(room.roomSummary().toOptional())
}
fun liveRoomMembers(memberships: List<Membership>): Observable<List<RoomMember>> {
@ -41,6 +43,7 @@ class RxRoom(private val room: Room) {
fun liveTimelineEvent(eventId: String): Observable<Optional<TimelineEvent>> {
return room.getTimeLineEventLive(eventId).asObservable()
.startWith(room.getTimeLineEvent(eventId).toOptional())
}
fun liveReadMarker(): Observable<Optional<String>> {

View File

@ -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<Timeline.Listener>()
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
}

View File

@ -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)

View File

@ -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

View File

@ -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")

View File

@ -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<List<ToggleState>> = Uninitialized,
// For actions
val actions: Async<List<EventSharedAction>> = Uninitialized,
val actions: List<EventSharedAction> = 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<TimelineEvent>): 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<TimelineEvent>): List<EventSharedAction> {
val event = optionalEvent.getOrNull() ?: return emptyList()
val messageContent: MessageContent? = event.annotations?.editSummary?.aggregatedContent.toModel()
?: event.root.getClearContent().toModel()
private fun actionsForEvent(timelineEvent: TimelineEvent): List<EventSharedAction> {
val messageContent: MessageContent? = timelineEvent.annotations?.editSummary?.aggregatedContent.toModel()
?: timelineEvent.root.getClearContent().toModel()
val type = messageContent?.type
return arrayListOf<EventSharedAction>().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))
}
}
}