From aa0520d47dba1c2b0d50b946aef4784a6317d75f Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 22 Sep 2020 18:56:05 +0200 Subject: [PATCH] Start reworking draft (simplify) --- .../java/org/matrix/android/sdk/rx/RxRoom.kt | 7 +- .../sdk/api/session/room/send/DraftService.kt | 11 +- .../session/room/draft/DefaultDraftService.kt | 7 +- .../session/room/draft/DraftRepository.kt | 111 ++++++------------ .../home/room/detail/RoomDetailViewModel.kt | 61 ++-------- 5 files changed, 65 insertions(+), 132 deletions(-) diff --git a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt index 0717a10d03..2bb6c0ff69 100644 --- a/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt +++ b/matrix-sdk-android-rx/src/main/java/org/matrix/android/sdk/rx/RxRoom.kt @@ -101,8 +101,11 @@ class RxRoom(private val room: Room) { return room.getEventReadReceiptsLive(eventId).asObservable() } - fun liveDrafts(): Observable> { - return room.getDraftsLive().asObservable() + fun liveDraft(): Observable> { + return room.getDraftLive().asObservable() + .startWithCallable { + room.getDraft().toOptional() + } } fun liveNotificationState(): Observable { diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt index dc91d5177f..6ef73d6486 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/api/session/room/send/DraftService.kt @@ -20,6 +20,7 @@ package org.matrix.android.sdk.api.session.room.send import androidx.lifecycle.LiveData import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.api.util.Optional interface DraftService { @@ -34,8 +35,12 @@ interface DraftService { fun deleteDraft(callback: MatrixCallback): Cancelable /** - * Return the current drafts if any, as a live data - * The draft list can contain one draft for {regular, reply, quote} and an arbitrary number of {edit} drafts + * Return the current draft or null */ - fun getDraftsLive(): LiveData> + fun getDraft(): UserDraft? + + /** + * Return the current draft if any, as a live data + */ + fun getDraftLive(): LiveData> } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt index dafa7df0eb..0426ee13e1 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DefaultDraftService.kt @@ -24,6 +24,7 @@ import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.session.room.send.DraftService import org.matrix.android.sdk.api.session.room.send.UserDraft import org.matrix.android.sdk.api.util.Cancelable +import org.matrix.android.sdk.api.util.Optional import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers @@ -55,7 +56,11 @@ internal class DefaultDraftService @AssistedInject constructor(@Assisted private } } - override fun getDraftsLive(): LiveData> { + override fun getDraft(): UserDraft? { + return draftRepository.getDraft(roomId) + } + + override fun getDraftLive(): LiveData> { return draftRepository.getDraftsLive(roomId) } } diff --git a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt index bc50b2d990..9262eb2f49 100644 --- a/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt +++ b/matrix-sdk-android/src/main/java/org/matrix/android/sdk/internal/session/room/draft/DraftRepository.kt @@ -20,35 +20,61 @@ package org.matrix.android.sdk.internal.session.room.draft import androidx.lifecycle.LiveData import androidx.lifecycle.Transformations import com.zhuinden.monarchy.Monarchy +import io.realm.Realm +import io.realm.kotlin.createObject import org.matrix.android.sdk.BuildConfig import org.matrix.android.sdk.api.session.room.send.UserDraft +import org.matrix.android.sdk.api.util.Optional +import org.matrix.android.sdk.api.util.toOptional import org.matrix.android.sdk.internal.database.mapper.DraftMapper -import org.matrix.android.sdk.internal.database.model.DraftEntity import org.matrix.android.sdk.internal.database.model.RoomSummaryEntity import org.matrix.android.sdk.internal.database.model.UserDraftsEntity import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.util.awaitTransaction -import io.realm.Realm -import io.realm.kotlin.createObject import timber.log.Timber import javax.inject.Inject -class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy) { +internal class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: Monarchy) { suspend fun saveDraft(roomId: String, userDraft: UserDraft) { monarchy.awaitTransaction { - saveDraft(it, userDraft, roomId) + saveDraftInDb(it, userDraft, roomId) } } suspend fun deleteDraft(roomId: String) { monarchy.awaitTransaction { - deleteDraft(it, roomId) + deleteDraftFromDb(it, roomId) } } - private fun deleteDraft(realm: Realm, roomId: String) { + fun getDraft(roomId: String): UserDraft? { + return Realm.getInstance(monarchy.realmConfiguration).use { realm -> + UserDraftsEntity.where(realm, roomId).findFirst() + ?.userDrafts + ?.firstOrNull() + ?.let { + DraftMapper.map(it) + } + } + } + + fun getDraftsLive(roomId: String): LiveData> { + val liveData = monarchy.findAllMappedWithChanges( + { UserDraftsEntity.where(it, roomId) }, + { + it.userDrafts.map { draft -> + DraftMapper.map(draft) + } + } + ) + return Transformations.map(liveData) { + it.firstOrNull()?.firstOrNull().toOptional() + } + } + + private fun deleteDraftFromDb(realm: Realm, roomId: String) { UserDraftsEntity.where(realm, roomId).findFirst()?.let { userDraftsEntity -> if (userDraftsEntity.userDrafts.isNotEmpty()) { userDraftsEntity.userDrafts.removeAt(userDraftsEntity.userDrafts.size - 1) @@ -56,7 +82,7 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: } } - private fun saveDraft(realm: Realm, draft: UserDraft, roomId: String) { + private fun saveDraftInDb(realm: Realm, draft: UserDraft, roomId: String) { val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst() ?: realm.createObject(roomId) @@ -68,62 +94,15 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: userDraftsEntity.let { userDraftEntity -> // Save only valid draft if (draft.isValid()) { - // Add a new draft or update the current one? + // Replace the current draft val newDraft = DraftMapper.map(draft) - - // Is it an update of the top draft? - val topDraft = userDraftEntity.userDrafts.lastOrNull() - - if (topDraft == null) { - Timber.d("Draft: create a new draft ${privacySafe(draft)}") - userDraftEntity.userDrafts.add(newDraft) - } else if (topDraft.draftMode == DraftEntity.MODE_EDIT) { - // top draft is an edit - if (newDraft.draftMode == DraftEntity.MODE_EDIT) { - if (topDraft.linkedEventId == newDraft.linkedEventId) { - // Update the top draft - Timber.d("Draft: update the top edit draft ${privacySafe(draft)}") - topDraft.content = newDraft.content - } else { - // Check a previously EDIT draft with the same id - val existingEditDraftOfSameEvent = userDraftEntity.userDrafts.find { - it.draftMode == DraftEntity.MODE_EDIT && it.linkedEventId == newDraft.linkedEventId - } - - if (existingEditDraftOfSameEvent != null) { - // Ignore the new text, restore what was typed before, by putting the draft to the top - Timber.d("Draft: restore a previously edit draft ${privacySafe(draft)}") - userDraftEntity.userDrafts.remove(existingEditDraftOfSameEvent) - userDraftEntity.userDrafts.add(existingEditDraftOfSameEvent) - } else { - Timber.d("Draft: add a new edit draft ${privacySafe(draft)}") - userDraftEntity.userDrafts.add(newDraft) - } - } - } else { - // Add a new regular draft to the top - Timber.d("Draft: add a new draft ${privacySafe(draft)}") - userDraftEntity.userDrafts.add(newDraft) - } - } else { - // Top draft is not an edit - if (newDraft.draftMode == DraftEntity.MODE_EDIT) { - Timber.d("Draft: create a new edit draft ${privacySafe(draft)}") - userDraftEntity.userDrafts.add(newDraft) - } else { - // Update the top draft - Timber.d("Draft: update the top draft ${privacySafe(draft)}") - topDraft.draftMode = newDraft.draftMode - topDraft.content = newDraft.content - topDraft.linkedEventId = newDraft.linkedEventId - } - } + Timber.d("Draft: create a new draft ${privacySafe(draft)}") + userDraftEntity.userDrafts.clear() + userDraftEntity.userDrafts.add(newDraft) } else { // There is no draft to save, so the composer was clear Timber.d("Draft: delete a draft") - val topDraft = userDraftEntity.userDrafts.lastOrNull() - if (topDraft == null) { Timber.d("Draft: nothing to do") } else { @@ -135,20 +114,6 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy: } } - fun getDraftsLive(roomId: String): LiveData> { - val liveData = monarchy.findAllMappedWithChanges( - { UserDraftsEntity.where(it, roomId) }, - { - it.userDrafts.map { draft -> - DraftMapper.map(draft) - } - } - ) - return Transformations.map(liveData) { - it.firstOrNull().orEmpty() - } - } - private fun privacySafe(o: Any): Any { if (BuildConfig.LOG_PRIVATE_DATA) { return o diff --git a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt index 1b5e928843..42ce8567cd 100644 --- a/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt +++ b/vector/src/main/java/im/vector/app/features/home/room/detail/RoomDetailViewModel.kt @@ -680,7 +680,7 @@ class RoomDetailViewModel @AssistedInject constructor( } }.exhaustive } - is SendMode.EDIT -> { + is SendMode.EDIT -> { // is original event a reply? val inReplyTo = state.sendMode.timelineEvent.root.getClearContent().toModel()?.relatesTo?.inReplyTo?.eventId ?: state.sendMode.timelineEvent.root.content.toModel()?.relatesTo?.inReplyTo?.eventId @@ -706,7 +706,7 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.MessageSent) popDraft() } - is SendMode.QUOTE -> { + is SendMode.QUOTE -> { val messageContent: MessageContent? = state.sendMode.timelineEvent.annotations?.editSummary?.aggregatedContent.toModel() ?: state.sendMode.timelineEvent.root.getClearContent().toModel() @@ -729,7 +729,7 @@ class RoomDetailViewModel @AssistedInject constructor( _viewEvents.post(RoomDetailViewEvents.MessageSent) popDraft() } - is SendMode.REPLY -> { + is SendMode.REPLY -> { state.sendMode.timelineEvent.let { room.replyToMessage(it, action.text.toString(), action.autoMarkdown) _viewEvents.post(RoomDetailViewEvents.MessageSent) @@ -741,6 +741,9 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun popDraft() { + setState { + copy(sendMode = SendMode.REGULAR("")) + } room.deleteDraft(NoOpMatrixCallback()) } @@ -915,73 +918,25 @@ class RoomDetailViewModel @AssistedInject constructor( } private fun handleEditAction(action: RoomDetailAction.EnterEditMode) { - saveCurrentDraft(action.text) - room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> - setState { copy(sendMode = SendMode.EDIT(timelineEvent, action.text)) } - timelineEvent.root.eventId?.let { - room.saveDraft(UserDraft.EDIT(it, timelineEvent.getTextEditableContent() ?: ""), NoOpMatrixCallback()) - } + setState { copy(sendMode = SendMode.EDIT(timelineEvent, timelineEvent.getTextEditableContent() ?: "")) } } } private fun handleQuoteAction(action: RoomDetailAction.EnterQuoteMode) { - saveCurrentDraft(action.text) - room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> setState { copy(sendMode = SendMode.QUOTE(timelineEvent, action.text)) } - withState { state -> - // Save a new draft and keep the previously entered text, if it was not an edit - timelineEvent.root.eventId?.let { - if (state.sendMode is SendMode.EDIT) { - room.saveDraft(UserDraft.QUOTE(it, ""), NoOpMatrixCallback()) - } else { - room.saveDraft(UserDraft.QUOTE(it, action.text), NoOpMatrixCallback()) - } - } - } } } private fun handleReplyAction(action: RoomDetailAction.EnterReplyMode) { - saveCurrentDraft(action.text) - room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> setState { copy(sendMode = SendMode.REPLY(timelineEvent, action.text)) } - withState { state -> - // Save a new draft and keep the previously entered text, if it was not an edit - timelineEvent.root.eventId?.let { - if (state.sendMode is SendMode.EDIT) { - room.saveDraft(UserDraft.REPLY(it, ""), NoOpMatrixCallback()) - } else { - room.saveDraft(UserDraft.REPLY(it, action.text), NoOpMatrixCallback()) - } - } - } - } - } - - private fun saveCurrentDraft(draft: String) { - // Save the draft with the current text if any - withState { - if (draft.isNotBlank()) { - when (it.sendMode) { - is SendMode.REGULAR -> room.saveDraft(UserDraft.REGULAR(draft), NoOpMatrixCallback()) - is SendMode.REPLY -> room.saveDraft(UserDraft.REPLY(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback()) - is SendMode.QUOTE -> room.saveDraft(UserDraft.QUOTE(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback()) - is SendMode.EDIT -> room.saveDraft(UserDraft.EDIT(it.sendMode.timelineEvent.root.eventId!!, draft), NoOpMatrixCallback()) - } - } } } private fun handleExitSpecialMode(action: RoomDetailAction.ExitSpecialMode) = withState { - if (it.sendMode is SendMode.EDIT) { - room.deleteDraft(NoOpMatrixCallback()) - } else { - // Save a new draft and keep the previously entered text - room.saveDraft(UserDraft.REGULAR(action.text), NoOpMatrixCallback()) - } + room.deleteDraft(NoOpMatrixCallback()) setState { copy(sendMode = SendMode.REGULAR(action.text)) } }