Start reworking draft (simplify)
This commit is contained in:
parent
f030e098a8
commit
aa0520d47d
|
@ -101,8 +101,11 @@ class RxRoom(private val room: Room) {
|
|||
return room.getEventReadReceiptsLive(eventId).asObservable()
|
||||
}
|
||||
|
||||
fun liveDrafts(): Observable<List<UserDraft>> {
|
||||
return room.getDraftsLive().asObservable()
|
||||
fun liveDraft(): Observable<Optional<UserDraft>> {
|
||||
return room.getDraftLive().asObservable()
|
||||
.startWithCallable {
|
||||
room.getDraft().toOptional()
|
||||
}
|
||||
}
|
||||
|
||||
fun liveNotificationState(): Observable<RoomNotificationState> {
|
||||
|
|
|
@ -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<Unit>): 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<List<UserDraft>>
|
||||
fun getDraft(): UserDraft?
|
||||
|
||||
/**
|
||||
* Return the current draft if any, as a live data
|
||||
*/
|
||||
fun getDraftLive(): LiveData<Optional<UserDraft>>
|
||||
}
|
||||
|
|
|
@ -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<List<UserDraft>> {
|
||||
override fun getDraft(): UserDraft? {
|
||||
return draftRepository.getDraft(roomId)
|
||||
}
|
||||
|
||||
override fun getDraftLive(): LiveData<Optional<UserDraft>> {
|
||||
return draftRepository.getDraftsLive(roomId)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -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<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 ->
|
||||
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<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 {
|
||||
if (BuildConfig.LOG_PRIVATE_DATA) {
|
||||
return o
|
||||
|
|
|
@ -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<MessageContent>()?.relatesTo?.inReplyTo?.eventId
|
||||
?: state.sendMode.timelineEvent.root.content.toModel<EncryptedEventContent>()?.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)) }
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in New Issue