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:
parent
f9487f8995
commit
f09bf61750
|
@ -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>> {
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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)
|
||||
|
|
|
@ -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
|
||||
|
|
|
@ -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")
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue