Start reworking draft (simplify)

This commit is contained in:
ganfra 2020-09-22 18:56:05 +02:00 committed by Benoit Marty
parent f030e098a8
commit aa0520d47d
5 changed files with 65 additions and 132 deletions

View file

@ -101,8 +101,11 @@ class RxRoom(private val room: Room) {
return room.getEventReadReceiptsLive(eventId).asObservable() return room.getEventReadReceiptsLive(eventId).asObservable()
} }
fun liveDrafts(): Observable<List<UserDraft>> { fun liveDraft(): Observable<Optional<UserDraft>> {
return room.getDraftsLive().asObservable() return room.getDraftLive().asObservable()
.startWithCallable {
room.getDraft().toOptional()
}
} }
fun liveNotificationState(): Observable<RoomNotificationState> { fun liveNotificationState(): Observable<RoomNotificationState> {

View file

@ -20,6 +20,7 @@ package org.matrix.android.sdk.api.session.room.send
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import org.matrix.android.sdk.api.MatrixCallback import org.matrix.android.sdk.api.MatrixCallback
import org.matrix.android.sdk.api.util.Cancelable import org.matrix.android.sdk.api.util.Cancelable
import org.matrix.android.sdk.api.util.Optional
interface DraftService { interface DraftService {
@ -34,8 +35,12 @@ interface DraftService {
fun deleteDraft(callback: MatrixCallback<Unit>): Cancelable fun deleteDraft(callback: MatrixCallback<Unit>): Cancelable
/** /**
* Return the current drafts if any, as a live data * Return the current draft or null
* The draft list can contain one draft for {regular, reply, quote} and an arbitrary number of {edit} drafts
*/ */
fun getDraftsLive(): LiveData<List<UserDraft>> fun getDraft(): UserDraft?
/**
* Return the current draft if any, as a live data
*/
fun getDraftLive(): LiveData<Optional<UserDraft>>
} }

View file

@ -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.DraftService
import org.matrix.android.sdk.api.session.room.send.UserDraft 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.Cancelable
import org.matrix.android.sdk.api.util.Optional
import org.matrix.android.sdk.internal.task.TaskExecutor import org.matrix.android.sdk.internal.task.TaskExecutor
import org.matrix.android.sdk.internal.task.launchToCallback import org.matrix.android.sdk.internal.task.launchToCallback
import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers import org.matrix.android.sdk.internal.util.MatrixCoroutineDispatchers
@ -55,7 +56,11 @@ internal class DefaultDraftService @AssistedInject constructor(@Assisted private
} }
} }
override fun getDraftsLive(): LiveData<List<UserDraft>> { override fun getDraft(): UserDraft? {
return draftRepository.getDraft(roomId)
}
override fun getDraftLive(): LiveData<Optional<UserDraft>> {
return draftRepository.getDraftsLive(roomId) return draftRepository.getDraftsLive(roomId)
} }
} }

View file

@ -20,35 +20,61 @@ package org.matrix.android.sdk.internal.session.room.draft
import androidx.lifecycle.LiveData import androidx.lifecycle.LiveData
import androidx.lifecycle.Transformations import androidx.lifecycle.Transformations
import com.zhuinden.monarchy.Monarchy 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.BuildConfig
import org.matrix.android.sdk.api.session.room.send.UserDraft 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.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.RoomSummaryEntity
import org.matrix.android.sdk.internal.database.model.UserDraftsEntity import org.matrix.android.sdk.internal.database.model.UserDraftsEntity
import org.matrix.android.sdk.internal.database.query.where import org.matrix.android.sdk.internal.database.query.where
import org.matrix.android.sdk.internal.di.SessionDatabase import org.matrix.android.sdk.internal.di.SessionDatabase
import org.matrix.android.sdk.internal.util.awaitTransaction import org.matrix.android.sdk.internal.util.awaitTransaction
import io.realm.Realm
import io.realm.kotlin.createObject
import timber.log.Timber import timber.log.Timber
import javax.inject.Inject 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) { suspend fun saveDraft(roomId: String, userDraft: UserDraft) {
monarchy.awaitTransaction { monarchy.awaitTransaction {
saveDraft(it, userDraft, roomId) saveDraftInDb(it, userDraft, roomId)
} }
} }
suspend fun deleteDraft(roomId: String) { suspend fun deleteDraft(roomId: String) {
monarchy.awaitTransaction { 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<Optional<UserDraft>> {
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 -> UserDraftsEntity.where(realm, roomId).findFirst()?.let { userDraftsEntity ->
if (userDraftsEntity.userDrafts.isNotEmpty()) { if (userDraftsEntity.userDrafts.isNotEmpty()) {
userDraftsEntity.userDrafts.removeAt(userDraftsEntity.userDrafts.size - 1) 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() val roomSummaryEntity = RoomSummaryEntity.where(realm, roomId).findFirst()
?: realm.createObject(roomId) ?: realm.createObject(roomId)
@ -68,62 +94,15 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy:
userDraftsEntity.let { userDraftEntity -> userDraftsEntity.let { userDraftEntity ->
// Save only valid draft // Save only valid draft
if (draft.isValid()) { if (draft.isValid()) {
// Add a new draft or update the current one? // Replace the current draft
val newDraft = DraftMapper.map(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)}") Timber.d("Draft: create a new draft ${privacySafe(draft)}")
userDraftEntity.userDrafts.clear()
userDraftEntity.userDrafts.add(newDraft) 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
}
}
} else { } else {
// There is no draft to save, so the composer was clear // There is no draft to save, so the composer was clear
Timber.d("Draft: delete a draft") Timber.d("Draft: delete a draft")
val topDraft = userDraftEntity.userDrafts.lastOrNull() val topDraft = userDraftEntity.userDrafts.lastOrNull()
if (topDraft == null) { if (topDraft == null) {
Timber.d("Draft: nothing to do") Timber.d("Draft: nothing to do")
} else { } else {
@ -135,20 +114,6 @@ class DraftRepository @Inject constructor(@SessionDatabase private val monarchy:
} }
} }
fun getDraftsLive(roomId: String): LiveData<List<UserDraft>> {
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 { private fun privacySafe(o: Any): Any {
if (BuildConfig.LOG_PRIVATE_DATA) { if (BuildConfig.LOG_PRIVATE_DATA) {
return o return o

View file

@ -741,6 +741,9 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
private fun popDraft() { private fun popDraft() {
setState {
copy(sendMode = SendMode.REGULAR(""))
}
room.deleteDraft(NoOpMatrixCallback()) room.deleteDraft(NoOpMatrixCallback())
} }
@ -915,73 +918,25 @@ class RoomDetailViewModel @AssistedInject constructor(
} }
private fun handleEditAction(action: RoomDetailAction.EnterEditMode) { private fun handleEditAction(action: RoomDetailAction.EnterEditMode) {
saveCurrentDraft(action.text)
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.EDIT(timelineEvent, action.text)) } setState { copy(sendMode = SendMode.EDIT(timelineEvent, timelineEvent.getTextEditableContent() ?: "")) }
timelineEvent.root.eventId?.let {
room.saveDraft(UserDraft.EDIT(it, timelineEvent.getTextEditableContent() ?: ""), NoOpMatrixCallback())
}
} }
} }
private fun handleQuoteAction(action: RoomDetailAction.EnterQuoteMode) { private fun handleQuoteAction(action: RoomDetailAction.EnterQuoteMode) {
saveCurrentDraft(action.text)
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.QUOTE(timelineEvent, action.text)) } 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) { private fun handleReplyAction(action: RoomDetailAction.EnterReplyMode) {
saveCurrentDraft(action.text)
room.getTimeLineEvent(action.eventId)?.let { timelineEvent -> room.getTimeLineEvent(action.eventId)?.let { timelineEvent ->
setState { copy(sendMode = SendMode.REPLY(timelineEvent, action.text)) } 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 { private fun handleExitSpecialMode(action: RoomDetailAction.ExitSpecialMode) = withState {
if (it.sendMode is SendMode.EDIT) {
room.deleteDraft(NoOpMatrixCallback()) room.deleteDraft(NoOpMatrixCallback())
} else {
// Save a new draft and keep the previously entered text
room.saveDraft(UserDraft.REGULAR(action.text), NoOpMatrixCallback())
}
setState { copy(sendMode = SendMode.REGULAR(action.text)) } setState { copy(sendMode = SendMode.REGULAR(action.text)) }
} }