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